#include "engine.h" VAR(oqdynent, 0, 1, 1); VAR(animationinterpolationtime, 0, 150, 1000); model *loadingmodel = NULL; #include "ragdoll.h" #include "animmodel.h" #include "skelmodel.h" static model *(__cdecl *modeltypes[NUMMODELTYPES])(const char *); static int addmodeltype(int type, model *(__cdecl *loader)(const char *)) { modeltypes[type] = loader; return type; } #define MODELTYPE(modeltype, modelclass) \ static model *__loadmodel__##modelclass(const char *filename) { \ \ return new modelclass(filename); \ } \ UNUSED static int __dummy__##modelclass = addmodeltype((modeltype), __loadmodel__##modelclass); #include "md5.h" #include "iqm.h" MODELTYPE(MDL_MD5, md5); MODELTYPE(MDL_IQM, iqm); #define checkmdl if(!loadingmodel) { conoutf(CON_ERROR, "not loading a model"); return; } void mdlcullface(int *cullface) { checkmdl; loadingmodel->setcullface(*cullface!=0); } COMMAND(mdlcullface, "i"); void mdlcollide(int *collide) { checkmdl; loadingmodel->collide = *collide!=0; } COMMAND(mdlcollide, "i"); void mdlellipsecollide(int *collide) { checkmdl; loadingmodel->ellipsecollide = *collide!=0; } COMMAND(mdlellipsecollide, "i"); void mdlspec(int *percent) { checkmdl; float spec = 1.0f; if(*percent>0) spec = *percent/100.0f; else if(*percent<0) spec = 0.0f; loadingmodel->setspec(spec); } COMMAND(mdlspec, "i"); void mdlambient(int *percent) { checkmdl; float ambient = 0.3f; if(*percent>0) ambient = *percent/100.0f; else if(*percent<0) ambient = 0.0f; loadingmodel->setambient(ambient); } COMMAND(mdlambient, "i"); void mdlalphatest(float *cutoff) { checkmdl; loadingmodel->setalphatest(max(0.0f, min(1.0f, *cutoff))); } COMMAND(mdlalphatest, "f"); void mdlalphablend(int *blend) { checkmdl; loadingmodel->setalphablend(*blend!=0); } COMMAND(mdlalphablend, "i"); void mdlalphadepth(int *depth) { checkmdl; loadingmodel->alphadepth = *depth!=0; } COMMAND(mdlalphadepth, "i"); void mdldepthoffset(int *offset) { checkmdl; loadingmodel->depthoffset = *offset!=0; } COMMAND(mdldepthoffset, "i"); void mdlfullbright(float *fullbright) { checkmdl; loadingmodel->setfullbright(*fullbright); } COMMAND(mdlfullbright, "f"); void mdlshader(char *shader) { checkmdl; loadingmodel->setshader(lookupshaderbyname(shader)); } COMMAND(mdlshader, "s"); void mdlspin(float *yaw, float *pitch) { checkmdl; loadingmodel->spinyaw = *yaw; loadingmodel->spinpitch = *pitch; } COMMAND(mdlspin, "ff"); void mdlscale(int *percent) { checkmdl; float scale = 1.0f; if(*percent>0) scale = *percent/100.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 mdlyaw(float *angle) { checkmdl; loadingmodel->offsetyaw = *angle; } COMMAND(mdlyaw, "f"); void mdlpitch(float *angle) { checkmdl; loadingmodel->offsetpitch = *angle; } COMMAND(mdlpitch, "f"); void mdlshadow(int *shadow) { checkmdl; loadingmodel->shadow = *shadow!=0; } COMMAND(mdlshadow, "i"); void mdlbb(float *rad, float *h, float *eyeheight) { checkmdl; loadingmodel->collidexyradius = *rad; loadingmodel->collideheight = *h; loadingmodel->eyeheight = *eyeheight; } COMMAND(mdlbb, "fff"); void mdlextendbb(float *x, float *y, float *z) { checkmdl; loadingmodel->bbextend = vec(*x, *y, *z); } COMMAND(mdlextendbb, "fff"); void mdlname() { checkmdl; result(loadingmodel->name); } COMMAND(mdlname, ""); #define checkragdoll \ checkmdl; \ if(!loadingmodel->skeletal()) { conoutf(CON_ERROR, "not loading a skeletal model"); return; } \ skelmodel *m = (skelmodel *)loadingmodel; \ if(m->parts.empty()) return; \ skelmodel::skelmeshgroup *meshes = (skelmodel::skelmeshgroup *)m->parts.last()->meshes; \ if(!meshes) return; \ skelmodel::skeleton *skel = meshes->skel; \ if(!skel->ragdoll) skel->ragdoll = new ragdollskel; \ ragdollskel *ragdoll = skel->ragdoll; \ if(ragdoll->loaded) return; void rdvert(float *x, float *y, float *z, float *radius) { checkragdoll; ragdollskel::vert &v = ragdoll->verts.add(); v.pos = vec(*x, *y, *z); v.radius = *radius > 0 ? *radius : 1; } COMMAND(rdvert, "ffff"); void rdeye(int *v) { checkragdoll; ragdoll->eye = *v; } COMMAND(rdeye, "i"); void rdtri(int *v1, int *v2, int *v3) { checkragdoll; ragdollskel::tri &t = ragdoll->tris.add(); t.vert[0] = *v1; t.vert[1] = *v2; t.vert[2] = *v3; } COMMAND(rdtri, "iii"); void rdjoint(int *n, int *t, int *v1, int *v2, int *v3) { checkragdoll; if(*n < 0 || *n >= skel->numbones) return; ragdollskel::joint &j = ragdoll->joints.add(); j.bone = *n; j.tri = *t; j.vert[0] = *v1; j.vert[1] = *v2; j.vert[2] = *v3; } COMMAND(rdjoint, "iibbb"); void rdlimitdist(int *v1, int *v2, float *mindist, float *maxdist) { checkragdoll; ragdollskel::distlimit &d = ragdoll->distlimits.add(); d.vert[0] = *v1; d.vert[1] = *v2; d.mindist = *mindist; d.maxdist = max(*maxdist, *mindist); } COMMAND(rdlimitdist, "iiff"); void rdlimitrot(int *t1, int *t2, float *maxangle, float *qx, float *qy, float *qz, float *qw) { checkragdoll; ragdollskel::rotlimit &r = ragdoll->rotlimits.add(); r.tri[0] = *t1; r.tri[1] = *t2; r.maxangle = *maxangle * RAD; r.middle = matrix3(quat(*qx, *qy, *qz, *qw)); } COMMAND(rdlimitrot, "iifffff"); void rdanimjoints(int *on) { checkragdoll; ragdoll->animjoints = *on!=0; } COMMAND(rdanimjoints, "i"); // mapmodels vector mapmodels; void mmodel(char *name) { mapmodelinfo &mmi = mapmodels.add(); copystring(mmi.name, name); mmi.m = NULL; } void mapmodelcompat(int *rad, int *h, int *tex, char *name, char *shadow) { mmodel(name); } void mapmodelreset(int *n) { if(!(identflags&IDF_OVERRIDDEN) && !game::allowedittoggle()) return; mapmodels.shrink(clamp(*n, 0, mapmodels.length())); } mapmodelinfo *getmminfo(int i) { return mapmodels.inrange(i) ? &mapmodels[i] : 0; } const char *mapmodelname(int i) { return mapmodels.inrange(i) ? mapmodels[i].name : NULL; } COMMAND(mmodel, "s"); COMMANDN(mapmodel, mapmodelcompat, "iiiss"); COMMAND(mapmodelreset, "i"); ICOMMAND(mapmodelname, "i", (int *index), { result(mapmodels.inrange(*index) ? mapmodels[*index].name : ""); }); ICOMMAND(mapmodelloaded, "i", (int *index), { intret(mapmodels.inrange(*index) && mapmodels[*index].m ? 1 : 0); }); ICOMMAND(nummapmodels, "", (), { intret(mapmodels.length()); }); ICOMMAND(mapmodelfind, "s", (char *name), { int found = -1; loopv(mapmodels) if(strstr(mapmodels[i].name, name)) { found = i; break; } intret(found); }); // model registry hashnameset models; vector preloadmodels; void preloadmodel(const char *name) { if(!name || !name[0] || models.access(name)) return; preloadmodels.add(newstring(name)); } void flushpreloadedmodels(bool msg) { loopv(preloadmodels) { loadprogress = float(i+1)/preloadmodels.length(); model *m = loadmodel(preloadmodels[i], -1, msg); if(!m) { if(msg) conoutf(CON_WARN, "could not load model: %s", preloadmodels[i]); } else { m->preloadmeshes(); } } preloadmodels.deletearrays(); loadprogress = 0; } void preloadusedmapmodels(bool msg, bool bih) { vector &ents = entities::getents(); vector mapmodels; loopv(ents) { extentity &e = *ents[i]; if(e.type==ET_MAPMODEL && e.attr2 >= 0 && mapmodels.find(e.attr2) < 0) mapmodels.add(e.attr2); } loopv(mapmodels) { loadprogress = float(i+1)/mapmodels.length(); int mmindex = mapmodels[i]; mapmodelinfo *mmi = getmminfo(mmindex); if(!mmi) { if(msg) conoutf(CON_WARN, "could not find map model: %d", mmindex); } else if(mmi->name[0] && !loadmodel(NULL, mmindex, msg)) { if(msg) conoutf(CON_WARN, "could not load model: %s", mmi->name); } else if(mmi->m) { if(bih) mmi->m->preloadBIH(); mmi->m->preloadmeshes(); } } loadprogress = 0; } bool modelloaded(const char *name) { return models.find(name, NULL) != NULL; } model *loadmodel(const char *name, int i, bool msg) { if(!name) { if(!mapmodels.inrange(i)) return NULL; mapmodelinfo &mmi = mapmodels[i]; if(mmi.m) return mmi.m; name = mmi.name; } model **mm = models.access(name); model *m; if(mm) m = *mm; else { if(!name[0] || loadingmodel || lightmapping > 1) return NULL; if(msg) { defformatstring(filename, "packages/models/%s", name); renderprogress(loadprogress, filename); } loopi(NUMMODELTYPES) { m = modeltypes[i](name); if(!m) continue; loadingmodel = m; if(m->load()) break; DELETEP(m); } loadingmodel = NULL; if(!m) return NULL; models.access(m->name, m); m->preloadshaders(); } if(mapmodels.inrange(i) && !mapmodels[i].m) mapmodels[i].m = m; return m; } void preloadmodelshaders() { if(initing) return; enumerate(models, model *, m, m->preloadshaders()); } void clear_mdls() { enumerate(models, model *, m, delete m); } void cleanupmodels() { enumerate(models, model *, m, m->cleanup()); } void clearmodel(char *name) { model **m = models.access(name); if(!m) { conoutf(CON_WARN, "model %s is not loaded", name); return; } loopv(mapmodels) if(mapmodels[i].m==*m) mapmodels[i].m = NULL; models.remove(name); (*m)->cleanup(); delete *m; conoutf("cleared model %s", name); } COMMAND(clearmodel, "s"); bool modeloccluded(const vec ¢er, float radius) { ivec bbmin(vec(center).sub(radius)), bbmax(ivec(center).add(radius+1)); return bboccluded(bbmin, bbmax); } VAR(showboundingbox, 0, 0, 2); void render2dbox(vec &o, float x, float y, float z) { gle::begin(GL_LINE_LOOP); gle::attribf(o.x, o.y, o.z); gle::attribf(o.x, o.y, o.z+z); gle::attribf(o.x+x, o.y+y, o.z+z); gle::attribf(o.x+x, o.y+y, o.z); xtraverts += gle::end(); } void render3dbox(vec &o, float tofloor, float toceil, float xradius, float yradius) { if(yradius<=0) yradius = xradius; vec c = o; c.sub(vec(xradius, yradius, tofloor)); float xsz = xradius*2, ysz = yradius*2; float h = tofloor+toceil; gle::colorf(1, 1, 1); gle::defvertex(); render2dbox(c, xsz, 0, h); render2dbox(c, 0, ysz, h); c.add(vec(xsz, ysz, 0)); render2dbox(c, -xsz, 0, h); render2dbox(c, 0, -ysz, h); } void renderellipse(vec &o, float xradius, float yradius, float yaw) { gle::colorf(0.5f, 0.5f, 0.5f); gle::defvertex(); gle::begin(GL_LINE_LOOP); loopi(15) { const vec2 &sc = sincos360[i*(360/15)]; gle::attrib(vec(xradius*sc.x, yradius*sc.y, 0).rotate_around_z((yaw+90)*RAD).add(o)); } xtraverts += gle::end(); } struct batchedmodel { vec pos, color, dir; int anim; float yaw, pitch, transparent; int basetime, basetime2, flags; dynent *d; int attached; occludequery *query; }; struct modelbatch { model *m; int flags; vector batched; }; static vector batches; static vector modelattached; static int numbatches = -1; static occludequery *modelquery = NULL; void startmodelbatches() { numbatches = 0; modelattached.setsize(0); } modelbatch &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; b->flags = 0; m->batch = numbatches++; } return *b; } void renderbatchedmodel(model *m, batchedmodel &b) { modelattach *a = NULL; if(b.attached>=0) a = &modelattached[b.attached]; int anim = b.anim; if(shadowmapping) { anim |= ANIM_NOSKIN; GLOBALPARAMF(shadowintensity, b.transparent); } else { if(b.flags&MDL_FULLBRIGHT) anim |= ANIM_FULLBRIGHT; if(b.flags&MDL_GHOST) anim |= ANIM_GHOST; } m->render(anim, b.basetime, b.basetime2, b.pos, b.yaw, b.pitch, b.d, a, b.color, b.dir, b.transparent); } struct transparentmodel { model *m; batchedmodel *batched; float dist; }; static inline bool sorttransparentmodels(const transparentmodel &x, const transparentmodel &y) { return x.dist < y.dist; } void endmodelbatches() { vector transparent; loopi(numbatches) { modelbatch &b = *batches[i]; if(b.batched.empty()) continue; bool rendered = false; occludequery *query = NULL; if(b.flags&MDL_GHOST) { loopvj(b.batched) { batchedmodel &bm = b.batched[j]; if((bm.flags&(MDL_CULL_VFC|MDL_GHOST))!=MDL_GHOST || bm.query) continue; if(!rendered) { b.m->startrender(); rendered = true; } renderbatchedmodel(b.m, bm); } if(rendered) { b.m->endrender(); rendered = false; } } loopvj(b.batched) { batchedmodel &bm = b.batched[j]; if(bm.flags&(MDL_CULL_VFC|MDL_GHOST)) continue; if(bm.query!=query) { if(query) endquery(query); query = bm.query; if(query) startquery(query); } if(bm.transparent < 1 && (!query || query->owner==bm.d) && !shadowmapping) { transparentmodel &tm = transparent.add(); tm.m = b.m; tm.batched = &bm; tm.dist = camera1->o.dist(bm.d && bm.d->ragdoll ? bm.d->ragdoll->center : bm.pos); continue; } if(!rendered) { b.m->startrender(); rendered = true; } renderbatchedmodel(b.m, bm); } if(query) endquery(query); if(rendered) b.m->endrender(); } if(transparent.length()) { transparent.sort(sorttransparentmodels); model *lastmodel = NULL; occludequery *query = NULL; loopv(transparent) { transparentmodel &tm = transparent[i]; if(lastmodel!=tm.m) { if(lastmodel) lastmodel->endrender(); (lastmodel = tm.m)->startrender(); } if(query!=tm.batched->query) { if(query) endquery(query); query = tm.batched->query; if(query) startquery(query); } renderbatchedmodel(tm.m, *tm.batched); } if(query) endquery(query); if(lastmodel) lastmodel->endrender(); } numbatches = -1; } void startmodelquery(occludequery *query) { modelquery = query; } void endmodelquery() { int querybatches = 0; loopi(numbatches) { modelbatch &b = *batches[i]; if(b.batched.empty() || b.batched.last().query!=modelquery) continue; querybatches++; } if(querybatches<=1) { if(!querybatches) modelquery->fragments = 0; modelquery = NULL; return; } int minattached = modelattached.length(); startquery(modelquery); loopi(numbatches) { modelbatch &b = *batches[i]; if(b.batched.empty() || b.batched.last().query!=modelquery) continue; b.m->startrender(); do { batchedmodel &bm = b.batched.pop(); if(bm.attached>=0) minattached = min(minattached, bm.attached); renderbatchedmodel(b.m, bm); } while(b.batched.length() && b.batched.last().query==modelquery); b.m->endrender(); } endquery(modelquery); modelquery = NULL; modelattached.setsize(minattached); } VAR(maxmodelradiusdistance, 10, 200, 1000); static inline void enablecullmodelquery() { startbb(); } static inline void rendercullmodelquery(model *m, dynent *d, const vec ¢er, float radius) { if(fabs(camera1->o.x-center.x) < radius+1 && fabs(camera1->o.y-center.y) < radius+1 && fabs(camera1->o.z-center.z) < radius+1) { d->query = NULL; return; } d->query = newquery(d); if(!d->query) return; startquery(d->query); int br = int(radius*2)+1; drawbb(ivec(int(center.x-radius), int(center.y-radius), int(center.z-radius)), ivec(br, br, br)); endquery(d->query); } static inline void disablecullmodelquery() { endbb(); } static inline int cullmodel(model *m, const vec ¢er, float radius, int flags, dynent *d = NULL, bool shadow = false) { if(flags&MDL_CULL_DIST && center.dist(camera1->o)/radius>maxmodelradiusdistance) return MDL_CULL_DIST; if(flags&MDL_CULL_VFC) { if(shadowmapping && !isshadowmapcaster(center, radius)) return MDL_CULL_VFC; } if(shadowmapping) { if(d) { if(flags&MDL_CULL_OCCLUDED && d->occluded>=OCCLUDE_PARENT) return MDL_CULL_OCCLUDED; if(flags&MDL_CULL_QUERY && d->occluded+1>=OCCLUDE_BB && d->query && d->query->owner==d && checkquery(d->query)) return MDL_CULL_QUERY; } if(!addshadowmapcaster(center, radius, radius)) return MDL_CULL_VFC; } else if(flags&MDL_CULL_OCCLUDED && modeloccluded(center, radius)) { if(d) d->occluded = OCCLUDE_PARENT; return MDL_CULL_OCCLUDED; } else if(flags&MDL_CULL_QUERY && d->query && d->query->owner==d && checkquery(d->query)) { if(d->occludedoccluded++; return MDL_CULL_QUERY; } return 0; } void rendermodel(entitylight *light, const char *mdl, int anim, const vec &o, float yaw, float pitch, int flags, dynent *d, modelattach *a, int basetime, int basetime2, float trans) { if(shadowmapping && !(flags&(MDL_SHADOW|MDL_DYNSHADOW))) return; model *m = loadmodel(mdl); if(!m) return; vec center(0, 0, 0), bbradius(0, 0, 0); float radius = 0; bool shadow = !shadowmap && (flags&(MDL_SHADOW|MDL_DYNSHADOW)); if(flags&(MDL_CULL_VFC|MDL_CULL_DIST|MDL_CULL_OCCLUDED|MDL_CULL_QUERY|MDL_SHADOW|MDL_DYNSHADOW)) { if(flags&MDL_CULL_QUERY) { if(!oqfrags || !oqdynent || !d) flags &= ~MDL_CULL_QUERY; } m->boundbox(center, bbradius); radius = bbradius.magnitude(); if(d && d->ragdoll) { radius = max(radius, d->ragdoll->radius); center = d->ragdoll->center; } else { center.rotate_around_z(yaw*RAD); center.add(o); } int culled = cullmodel(m, center, radius, flags, d, shadow); if(culled) { if(culled&(MDL_CULL_OCCLUDED|MDL_CULL_QUERY) && flags&MDL_CULL_QUERY) { enablecullmodelquery(); rendercullmodelquery(m, d, center, radius); disablecullmodelquery(); } return; } if(shadowmapping) flags &= ~MDL_CULL_QUERY; } if(flags&MDL_NORENDER) anim |= ANIM_NORENDER; else if(showboundingbox && !shadowmapping && editmode) { notextureshader->set(); if(d && showboundingbox==1) { render3dbox(d->o, d->eyeheight, d->aboveeye, d->radius); renderellipse(d->o, d->xradius, d->yradius, d->yaw); } else { vec center, radius; if(showboundingbox==1) m->collisionbox(center, radius); else m->boundbox(center, radius); rotatebb(center, radius, int(yaw)); center.add(o); render3dbox(center, radius.z, radius.z, radius.x, radius.y); } } vec lightcolor(1, 1, 1), lightdir(0, 0, 1); if(!shadowmapping) { vec pos = o; if(d) { d->occluded = OCCLUDE_NOTHING; if(!light) light = &d->light; if(flags&MDL_LIGHT && light->millis!=lastmillis) { if(d->ragdoll) { pos = d->ragdoll->center; pos.z += radius/2; } else if(d->type < ENT_CAMERA) pos.z += 0.75f*(d->eyeheight + d->aboveeye); lightreaching(pos, light->color, light->dir, (flags&MDL_LIGHT_FAST)!=0); dynlightreaching(pos, light->color, light->dir, (flags&MDL_HUD)!=0); light->millis = lastmillis; } } else if(flags&MDL_LIGHT) { if(!light) { lightreaching(pos, lightcolor, lightdir, (flags&MDL_LIGHT_FAST)!=0); dynlightreaching(pos, lightcolor, lightdir, (flags&MDL_HUD)!=0); } else if(light->millis!=lastmillis) { lightreaching(pos, light->color, light->dir, (flags&MDL_LIGHT_FAST)!=0); dynlightreaching(pos, light->color, light->dir, (flags&MDL_HUD)!=0); light->millis = lastmillis; } } if(light) { lightcolor = light->color; lightdir = light->dir; } if(flags&MDL_DYNLIGHT) dynlightreaching(pos, lightcolor, lightdir, (flags&MDL_HUD)!=0); } 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) { modelbatch &mb = addbatchedmodel(m); batchedmodel &b = mb.batched.add(); b.query = modelquery; b.pos = o; b.color = lightcolor; b.dir = lightdir; b.anim = anim; b.yaw = yaw; b.pitch = pitch; b.basetime = basetime; b.basetime2 = basetime2; b.transparent = trans; b.flags = flags & ~(MDL_CULL_VFC | MDL_CULL_DIST | MDL_CULL_OCCLUDED); if(!shadow) { b.flags &= ~(MDL_SHADOW|MDL_DYNSHADOW); if(flags&MDL_CULL_VFC) b.flags |= MDL_CULL_VFC; } mb.flags |= b.flags; 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; } if(flags&MDL_CULL_QUERY) d->query = b.query = newquery(d); return; } m->startrender(); if(shadowmapping) { anim |= ANIM_NOSKIN; GLOBALPARAMF(shadowintensity, trans); } else { if(flags&MDL_FULLBRIGHT) anim |= ANIM_FULLBRIGHT; if(flags&MDL_GHOST) anim |= ANIM_GHOST; } if(flags&MDL_CULL_QUERY) { d->query = newquery(d); if(d->query) startquery(d->query); } m->render(anim, basetime, basetime2, o, yaw, pitch, d, a, lightcolor, lightdir, trans); if(flags&MDL_CULL_QUERY && d->query) endquery(d->query); m->endrender(); } void abovemodel(vec &o, const char *mdl) { model *m = loadmodel(mdl); if(!m) return; o.z += m->above(); } bool matchanim(const char *name, const char *pattern) { for(;; pattern++) { const char *s = name; char c; for(;; pattern++) { c = *pattern; if(!c || c=='|') break; else if(c=='*') { if(!*s || iscubespace(*s)) break; do s++; while(*s && !iscubespace(*s)); } else if(c!=*s) break; else s++; } if(!*s && (!c || c=='|')) return true; pattern = strchr(pattern, '|'); if(!pattern) break; } return false; } void findanims(const char *pattern, vector &anims) { loopi(sizeof(animnames)/sizeof(animnames[0])) if(matchanim(animnames[i], pattern)) anims.add(i); } ICOMMAND(findanims, "s", (char *name), { vector anims; findanims(name, anims); vector buf; string num; loopv(anims) { formatstring(num, "%d", anims[i]); if(i > 0) buf.add(' '); buf.put(num, strlen(num)); } buf.add('\0'); result(buf.getbuf()); }); void loadskin(const char *dir, const char *altdir, Texture *&skin, Texture *&masks) { // model skin sharing { #define ifnoload(tex, path) if((tex = textureload(path, 0, true, false))==notexture) #define tryload(tex, prefix, cmd, name) \ ifnoload(tex, makerelpath(mdir, name ".jpg", prefix, cmd)) { \ \ ifnoload(tex, makerelpath(mdir, name ".png", prefix, cmd)) { \ \ ifnoload(tex, makerelpath(maltdir, name ".jpg", prefix, cmd)) { \ \ ifnoload(tex, makerelpath(maltdir, name ".png", prefix, cmd)) return; \ } \ } \ } defformatstring(mdir, "packages/models/%s", dir); defformatstring(maltdir, "packages/models/%s", altdir); masks = notexture; tryload(skin, NULL, NULL, "skin"); tryload(masks, NULL, NULL, "masks"); } // convenient function that covers the usual anims for players/monsters/npcs VAR(animoverride, -1, 0, NUMANIMS-1); VAR(testanims, 0, 0, 1); VAR(testpitch, -90, 0, 90); void renderclient(dynent *d, const char *mdlname, modelattach *attachments, int hold, int attack, int attackdelay, int lastaction, int lastpain, float fade, bool ragdoll) { int anim = hold ? hold : ANIM_IDLE|ANIM_LOOP; float yaw = testanims && d==player ? 0 : d->yaw+90, pitch = testpitch && d==player ? testpitch : d->pitch; vec o = d->feetpos(); int basetime = 0; if(animoverride) anim = (animoverride<0 ? ANIM_ALL : animoverride)|ANIM_LOOP; else if(d->state==CS_DEAD) { anim = ANIM_DYING|ANIM_NOPITCH; basetime = lastpain; if(ragdoll) { if(!d->ragdoll || d->ragdoll->millis < basetime) { DELETEP(d->ragdoll); anim |= ANIM_RAGDOLL; } } else if(lastmillis-basetime>1000) anim = ANIM_DEAD|ANIM_LOOP|ANIM_NOPITCH; } else if(d->state==CS_EDITING || d->state==CS_SPECTATOR) anim = ANIM_EDIT|ANIM_LOOP; else if(d->state==CS_LAGGED) anim = ANIM_LAG|ANIM_LOOP; else { if(lastmillis-lastpain < 300) { anim = ANIM_PAIN; basetime = lastpain; } else if(lastpain < lastaction && (attack < 0 || (d->type != ENT_AI && lastmillis-lastaction < attackdelay))) { anim = attack < 0 ? -attack : attack; basetime = lastaction; } if(d->inwater && d->physstate<=PHYS_FALL) anim |= (((game::allowmove(d) && (d->move || d->strafe)) || d->vel.z+d->falling.z>0 ? ANIM_SWIM : ANIM_SINK)|ANIM_LOOP)<timeinair>100) anim |= (ANIM_JUMP|ANIM_END)<move || d->strafe)) { if(d->move>0) anim |= (ANIM_FORWARD|ANIM_LOOP)<strafe) { if(d->move<0) anim |= ((d->strafe>0 ? ANIM_RIGHT : ANIM_LEFT)|ANIM_REVERSE|ANIM_LOOP)<strafe>0 ? ANIM_LEFT : ANIM_RIGHT)|ANIM_LOOP)<move<0) anim |= (ANIM_BACKWARD|ANIM_LOOP)<>ANIM_SECONDARY)&ANIM_INDEX) anim >>= ANIM_SECONDARY; } if(d->ragdoll && (!ragdoll || (anim&ANIM_INDEX)!=ANIM_DYING)) DELETEP(d->ragdoll); if(!((anim>>ANIM_SECONDARY)&ANIM_INDEX)) anim |= (ANIM_IDLE|ANIM_LOOP)<type==ENT_PLAYER) flags |= MDL_FULLBRIGHT; else flags |= MDL_CULL_DIST; if(d->state==CS_LAGGED) fade = min(fade, 0.3f); else flags |= MDL_DYNSHADOW; if(drawtex == DRAWTEX_MODELPREVIEW) flags &= ~(MDL_LIGHT | MDL_FULLBRIGHT | MDL_CULL_VFC | MDL_CULL_OCCLUDED | MDL_CULL_QUERY | MDL_CULL_DIST | MDL_DYNSHADOW); rendermodel(NULL, mdlname, anim, o, yaw, pitch, flags, d, attachments, basetime, 0, fade); } void setbbfrommodel(dynent *d, const char *mdl) { model *m = loadmodel(mdl); if(!m) return; vec center, radius; m->collisionbox(center, radius); if(d->type==ENT_INANIMATE && !m->ellipsecollide) d->collidetype = COLLIDE_OBB; d->xradius = radius.x + fabs(center.x); d->yradius = radius.y + fabs(center.y); d->radius = d->collidetype==COLLIDE_OBB ? sqrtf(d->xradius*d->xradius + d->yradius*d->yradius) : max(d->xradius, d->yradius); d->eyeheight = (center.z-radius.z) + radius.z*2*m->eyeheight; d->aboveeye = radius.z*2*(1.0f-m->eyeheight); if (d->aboveeye + d->eyeheight <= 0.5f) { float zrad = (0.5f - (d->aboveeye + d->eyeheight)) / 2; d->aboveeye += zrad; d->eyeheight += zrad; } }