1 #include "cube.h"
2 
3 VARP(animationinterpolationtime, 0, 150, 1000);
4 
5 model *loadingmodel = NULL;
6 
7 #include "tristrip.h"
8 #include "modelcache.h"
9 #include "vertmodel.h"
10 #include "md2.h"
11 #include "md3.h"
12 
13 #define checkmdl if(!loadingmodel) { conoutf("not loading a model"); return; }
14 
mdlcullface(int * cullface)15 void mdlcullface(int *cullface)
16 {
17     checkmdl;
18     loadingmodel->cullface = *cullface!=0;
19 }
20 
21 COMMAND(mdlcullface, "i");
22 
mdlvertexlight(int * vertexlight)23 void mdlvertexlight(int *vertexlight)
24 {
25     checkmdl;
26     loadingmodel->vertexlight = *vertexlight!=0;
27 }
28 
29 COMMAND(mdlvertexlight, "i");
30 
mdltranslucent(int * translucency)31 void mdltranslucent(int *translucency)
32 {
33     checkmdl;
34     loadingmodel->translucency = *translucency/100.0f;
35 }
36 
37 COMMAND(mdltranslucent, "i");
38 
mdlalphatest(int * alphatest)39 void mdlalphatest(int *alphatest)
40 {
41     checkmdl;
42     loadingmodel->alphatest = *alphatest/100.0f;
43 }
44 
45 COMMAND(mdlalphatest, "i");
46 
mdlalphablend(int * alphablend)47 void mdlalphablend(int *alphablend) //ALX Alpha channel models
48 {
49     checkmdl;
50     loadingmodel->alphablend = *alphablend!=0;
51 }
52 COMMAND(mdlalphablend, "i");
53 
mdlscale(int * percent)54 void mdlscale(int *percent)
55 {
56     checkmdl;
57     float scale = 0.3f;
58     if(*percent>0) scale = *percent/100.0f;
59     else if(*percent<0) scale = 0.0f;
60     loadingmodel->scale = scale;
61 }
62 
63 COMMAND(mdlscale, "i");
64 
mdltrans(float * x,float * y,float * z)65 void mdltrans(float *x, float *y, float *z)
66 {
67     checkmdl;
68     loadingmodel->translate = vec(*x, *y, *z);
69 }
70 
71 COMMAND(mdltrans, "fff");
72 
mdlshadowdist(int * dist)73 void mdlshadowdist(int *dist)
74 {
75     checkmdl;
76     loadingmodel->shadowdist = *dist;
77 }
78 
79 COMMAND(mdlshadowdist, "i");
80 
mdlcachelimit(int * limit)81 void mdlcachelimit(int *limit)
82 {
83     checkmdl;
84     loadingmodel->cachelimit = *limit;
85 }
86 
87 COMMAND(mdlcachelimit, "i");
88 
89 vector<mapmodelinfo> mapmodels;
90 
mapmodel(int * rad,int * h,int * zoff,char * snap,char * name)91 void mapmodel(int *rad, int *h, int *zoff, char *snap, char *name)
92 {
93     mapmodelinfo &mmi = mapmodels.add();
94     mmi.rad = *rad;
95     mmi.h = *h;
96     mmi.zoff = *zoff;
97     mmi.m = NULL;
98     formatstring(mmi.name)("mapmodels/%s", name);
99 }
100 
mapmodelreset()101 void mapmodelreset()
102 {
103     if(execcontext==IEXC_MAPCFG) mapmodels.shrink(0);
104 }
105 
getmminfo(int i)106 mapmodelinfo &getmminfo(int i) { return mapmodels.inrange(i) ? mapmodels[i] : *(mapmodelinfo *)0; }
107 
108 COMMAND(mapmodel, "iiiss");
109 COMMAND(mapmodelreset, "");
110 
111 hashtable<const char *, model *> mdllookup;
112 model *nomodel = NULL;
113 
loadmodel(const char * name,int i,bool trydl)114 model *loadmodel(const char *name, int i, bool trydl)
115 {
116     if(!name)
117     {
118         if(!mapmodels.inrange(i)) return NULL;
119         mapmodelinfo &mmi = mapmodels[i];
120         if(mmi.m) return mmi.m;
121         name = mmi.name;
122     }
123     model **mm = mdllookup.access(name);
124     model *m;
125     if(mm) m = *mm;
126     else
127     {
128         pushscontext(IEXC_MDLCFG);
129         m = new md2(name);
130         loadingmodel = m;
131         if(!m->load())
132         {
133             delete m;
134             m = new md3(name);
135             loadingmodel = m;
136             if(!m->load())
137             {
138                 delete m;
139                 loadingmodel = NULL;
140                 if(trydl)
141                 {
142                     defformatstring(dl)("packages/models/%s", name);
143                     requirepackage(PCK_MAPMODEL, dl);
144                 }
145                 else
146                 {
147                     mdllookup.access(newstring(name), nomodel);
148                     conoutf("\f3failed to load model %s", name);
149                 }
150             }
151         }
152         popscontext();
153         if(!loadingmodel)
154         {
155             if(!trydl)
156             {
157                 conoutf(_("failed to load model %s"), name);
158                 if(!nomodel) nomodel = new md2("nomodel");
159                 m = nomodel;
160                 mdllookup.access(newstring(name), m);
161             }
162         }
163         else mdllookup.access(m->name(), m);
164         loadingmodel = NULL;
165     }
166     if(m == nomodel) return NULL;
167     if(mapmodels.inrange(i) && !mapmodels[i].m) mapmodels[i].m = m;
168     return m;
169 }
170 
cleanupmodels()171 void cleanupmodels()
172 {
173     enumerate(mdllookup, model *, m, m->cleanup());
174 }
175 
176 VARP(dynshadow, 0, 40, 100);
177 VARP(dynshadowdecay, 0, 1000, 3000);
178 
179 struct batchedmodel
180 {
181     vec o;
182     int anim, varseed, tex;
183     float yaw, pitch, speed;
184     int basetime;
185     playerent *d;
186     int attached;
187     float scale;
188 };
189 struct modelbatch
190 {
191     model *m;
192     vector<batchedmodel> batched;
193 };
194 static vector<modelbatch *> batches;
195 static vector<modelattach> modelattached;
196 static int numbatches = -1;
197 
startmodelbatches()198 void startmodelbatches()
199 {
200     numbatches = 0;
201     modelattached.setsize(0);
202 }
203 
addbatchedmodel(model * m)204 batchedmodel &addbatchedmodel(model *m)
205 {
206     modelbatch *b = NULL;
207     if(m->batch>=0 && m->batch<numbatches && batches[m->batch]->m==m) b = batches[m->batch];
208     else
209     {
210         if(numbatches<batches.length())
211         {
212             b = batches[numbatches];
213             b->batched.setsize(0);
214         }
215         else b = batches.add(new modelbatch);
216         b->m = m;
217         m->batch = numbatches++;
218     }
219     return b->batched.add();
220 }
221 
renderbatchedmodel(model * m,batchedmodel & b)222 void renderbatchedmodel(model *m, batchedmodel &b)
223 {
224     modelattach *a = NULL;
225     if(b.attached>=0) a = &modelattached[b.attached];
226 
227     if(stenciling)
228     {
229         m->render(b.anim|ANIM_NOSKIN, b.varseed, b.speed, b.basetime, b.o, b.yaw, b.pitch, b.d, a, b.scale);
230         return;
231     }
232 
233     int x = (int)b.o.x, y = (int)b.o.y;
234     if(!OUTBORD(x, y))
235     {
236         sqr *s = S(x, y);
237         glColor3ub(s->r, s->g, s->b);
238     }
239     else glColor3f(1, 1, 1);
240 
241     m->setskin(b.tex);
242 
243     if(b.anim&ANIM_TRANSLUCENT)
244     {
245         glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
246         m->render(b.anim|ANIM_NOSKIN, b.varseed, b.speed, b.basetime, b.o, b.yaw, b.pitch, b.d, a, b.scale);
247         glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
248 
249         glDepthFunc(GL_LEQUAL);
250         glEnable(GL_BLEND);
251         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
252 
253         GLfloat color[4];
254         glGetFloatv(GL_CURRENT_COLOR, color);
255         glColor4f(color[0], color[1], color[2], m->translucency);
256     }
257 
258     m->render(b.anim, b.varseed, b.speed, b.basetime, b.o, b.yaw, b.pitch, b.d, a, b.scale);
259 
260     if(b.anim&ANIM_TRANSLUCENT)
261     {
262         glDepthFunc(GL_LESS);
263         glDisable(GL_BLEND);
264     }
265 }
266 
renderbatchedmodelshadow(model * m,batchedmodel & b)267 void renderbatchedmodelshadow(model *m, batchedmodel &b)
268 {
269     int x = (int)b.o.x, y = (int)b.o.y;
270     if(OUTBORD(x, y)) return;
271     sqr *s = S(x, y);
272     vec center(b.o.x, b.o.y, s->floor);
273     if(s->type==FHF) center.z -= s->vdelta/4.0f;
274     if(dynshadowquad && center.z-0.1f>b.o.z) return;
275     center.z += 0.1f;
276     modelattach *a = NULL;
277     if(b.attached>=0) a = &modelattached[b.attached];
278     float intensity = dynshadow/100.0f;
279     if(dynshadowdecay) switch(b.anim&ANIM_INDEX)
280     {
281         case ANIM_DECAY:
282         case ANIM_LYING_DEAD:
283             intensity *= max(1.0f - float(lastmillis - b.basetime)/dynshadowdecay, 0.0f);
284             break;
285     }
286     glColor4f(0, 0, 0, intensity);
287     m->rendershadow(b.anim, b.varseed, b.speed, b.basetime, dynshadowquad ? center : b.o, b.yaw, a);
288 }
289 
sortbatchedmodels(const batchedmodel * x,const batchedmodel * y)290 static int sortbatchedmodels(const batchedmodel *x, const batchedmodel *y)
291 {
292     if(x->tex < y->tex) return -1;
293     if(x->tex > y->tex) return 1;
294     return 0;
295 }
296 
297 struct translucentmodel
298 {
299     model *m;
300     batchedmodel *batched;
301     float dist;
302 };
303 
sorttranslucentmodels(const translucentmodel * x,const translucentmodel * y)304 static int sorttranslucentmodels(const translucentmodel *x, const translucentmodel *y)
305 {
306     if(x->dist > y->dist) return -1;
307     if(x->dist < y->dist) return 1;
308     return 0;
309 }
310 
clearmodelbatches()311 void clearmodelbatches()
312 {
313     numbatches = -1;
314 }
315 
endmodelbatches(bool flush)316 void endmodelbatches(bool flush)
317 {
318     vector<translucentmodel> translucent;
319     loopi(numbatches)
320     {
321         modelbatch &b = *batches[i];
322         if(b.batched.empty()) continue;
323         loopvj(b.batched) if(b.batched[j].tex) { b.batched.sort(sortbatchedmodels); break; }
324         b.m->startrender();
325         loopvj(b.batched)
326         {
327             batchedmodel &bm = b.batched[j];
328             if(bm.anim&ANIM_TRANSLUCENT)
329             {
330                 translucentmodel &tm = translucent.add();
331                 tm.m = b.m;
332                 tm.batched = &bm;
333                 tm.dist = camera1->o.dist(bm.o);
334                 continue;
335             }
336             renderbatchedmodel(b.m, bm);
337         }
338         if(dynshadow && b.m->hasshadows() && (!reflecting || refracting) && (!stencilshadow || !hasstencil || stencilbits < 8))
339         {
340             loopvj(b.batched)
341             {
342                 batchedmodel &bm = b.batched[j];
343                 if(bm.anim&ANIM_TRANSLUCENT) continue;
344                 renderbatchedmodelshadow(b.m, bm);
345             }
346         }
347         b.m->endrender();
348     }
349     if(translucent.length())
350     {
351         translucent.sort(sorttranslucentmodels);
352         model *lastmodel = NULL;
353         loopv(translucent)
354         {
355             translucentmodel &tm = translucent[i];
356             if(lastmodel!=tm.m)
357             {
358                 if(lastmodel) lastmodel->endrender();
359                 (lastmodel = tm.m)->startrender();
360             }
361             renderbatchedmodel(tm.m, *tm.batched);
362         }
363         if(lastmodel) lastmodel->endrender();
364     }
365     if(flush) clearmodelbatches();
366 }
367 
368 const int dbgmbatch = 0;
369 //VAR(dbgmbatch, 0, 0, 1);
370 
371 VARP(popdeadplayers, 0, 0, 1);
rendermodel(const char * mdl,int anim,int tex,float rad,const vec & o,float yaw,float pitch,float speed,int basetime,playerent * d,modelattach * a,float scale)372 void rendermodel(const char *mdl, int anim, int tex, float rad, const vec &o, float yaw, float pitch, float speed, int basetime, playerent *d, modelattach *a, float scale)
373 {
374     if(popdeadplayers && d && a)
375     {
376         int acv = anim&ANIM_INDEX;
377         if( acv == ANIM_DECAY || acv == ANIM_LYING_DEAD || acv == ANIM_CROUCH_DEATH || acv == ANIM_DEATH ) return;
378     }
379     model *m = loadmodel(mdl);
380     if(!m || (stenciling && (m->shadowdist <= 0 || anim&ANIM_TRANSLUCENT))) return;
381 
382     if(rad >= 0)
383     {
384         if(!rad) rad = m->radius;
385         if(isoccluded(camera1->o.x, camera1->o.y, o.x-rad, o.y-rad, rad*2)) return;
386     }
387 
388     if(stenciling && d && !raycubelos(camera1->o, o, d->radius))
389     {
390         vec target(o);
391         target.z += d->eyeheight;
392         if(!raycubelos(camera1->o, target, d->radius)) return;
393     }
394 
395     int varseed = 0;
396     if(d) switch(anim&ANIM_INDEX)
397     {
398         case ANIM_DEATH:
399         case ANIM_LYING_DEAD: varseed = (int)(size_t)d + d->lastpain; break;
400         default: varseed = (int)(size_t)d + d->lastaction; break;
401     }
402 
403     if(a) for(int i = 0; a[i].tag; i++)
404     {
405         if(a[i].name) a[i].m = loadmodel(a[i].name);
406         //if(a[i].m && a[i].m->type()!=m->type()) a[i].m = NULL;
407     }
408 
409     if(numbatches>=0 && !dbgmbatch)
410     {
411         batchedmodel &b = addbatchedmodel(m);
412         b.o = o;
413         b.anim = anim;
414         b.varseed = varseed;
415         b.tex = tex;
416         b.yaw = yaw;
417         b.pitch = pitch;
418         b.speed = speed;
419         b.basetime = basetime;
420         b.d = d;
421         b.attached = a ? modelattached.length() : -1;
422         if(a) for(int i = 0;; i++) { modelattached.add(a[i]); if(!a[i].tag) break; }
423         b.scale = scale;
424         return;
425     }
426 
427     if(stenciling)
428     {
429         m->startrender();
430         m->render(anim|ANIM_NOSKIN, varseed, speed, basetime, o, yaw, pitch, d, a, scale);
431         m->endrender();
432         return;
433     }
434 
435     m->startrender();
436 
437     int x = (int)o.x, y = (int)o.y;
438     if(!OUTBORD(x, y))
439     {
440         sqr *s = S(x, y);
441         if(!(anim&ANIM_TRANSLUCENT) && dynshadow && m->hasshadows() && (!reflecting || refracting) && (!stencilshadow || !hasstencil || stencilbits < 8))
442         {
443             vec center(o.x, o.y, s->floor);
444             if(s->type==FHF) center.z -= s->vdelta/4.0f;
445             if(!dynshadowquad || center.z-0.1f<=o.z)
446             {
447                 center.z += 0.1f;
448                 float intensity = dynshadow/100.0f;
449                 if(dynshadowdecay) switch(anim&ANIM_INDEX)
450                 {
451                     case ANIM_DECAY:
452                     case ANIM_LYING_DEAD:
453                         intensity *= max(1.0f - float(lastmillis - basetime)/dynshadowdecay, 0.0f);
454                         break;
455                 }
456                 glColor4f(0, 0, 0, intensity);
457                 m->rendershadow(anim, varseed, speed, basetime, dynshadowquad ? center : o, yaw, a);
458             }
459         }
460         glColor3ub(s->r, s->g, s->b);
461     }
462     else glColor3f(1, 1, 1);
463 
464     m->setskin(tex);
465 
466     if(anim&ANIM_TRANSLUCENT)
467     {
468         glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
469         m->render(anim|ANIM_NOSKIN, varseed, speed, basetime, o, yaw, pitch, d, a, scale);
470         glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
471 
472         glDepthFunc(GL_LEQUAL);
473         glEnable(GL_BLEND);
474         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
475 
476         GLfloat color[4];
477         glGetFloatv(GL_CURRENT_COLOR, color);
478         glColor4f(color[0], color[1], color[2], m->translucency);
479     }
480 
481     m->render(anim, varseed, speed, basetime, o, yaw, pitch, d, a, scale);
482 
483     if(anim&ANIM_TRANSLUCENT)
484     {
485         glDepthFunc(GL_LESS);
486         glDisable(GL_BLEND);
487     }
488 
489     m->endrender();
490 }
491 
findanim(const char * name)492 int findanim(const char *name)
493 {
494     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" };
495     loopi(sizeof(names)/sizeof(names[0])) if(!strcmp(name, names[i])) return i;
496     return -1;
497 }
498 
loadskin(const char * dir,const char * altdir,Texture * & skin)499 void loadskin(const char *dir, const char *altdir, Texture *&skin) // model skin sharing
500 {
501     #define ifnoload if((skin = textureload(path))==notexture)
502     defformatstring(path)("packages/models/%s/skin.jpg", dir);
503     ifnoload
504     {
505         strcpy(path+strlen(path)-3, "png");
506         ifnoload
507         {
508             formatstring(path)("packages/models/%s/skin.jpg", altdir);
509             ifnoload
510             {
511                 strcpy(path+strlen(path)-3, "png");
512                 ifnoload return;
513             }
514         }
515     }
516 }
517 
preload_playermodels()518 void preload_playermodels()
519 {
520     model *playermdl = loadmodel("playermodels");
521     if(dynshadow && playermdl) playermdl->genshadows(8.0f, 4.0f);
522     loopi(NUMGUNS)
523     {
524         if (i==GUN_CPISTOL) continue; //RR 18/12/12 - Remove when cpistol is added.
525         defformatstring(widn)("modmdlvwep%d", i);
526         defformatstring(vwep)("weapons/%s/world", identexists(widn)?getalias(widn):guns[i].modelname);
527         model *vwepmdl = loadmodel(vwep);
528         if(dynshadow && vwepmdl) vwepmdl->genshadows(8.0f, 4.0f);
529     }
530 }
531 
preload_entmodels()532 void preload_entmodels()
533 {
534      string buf;
535 
536      extern const char *entmdlnames[];
537      loopi(I_AKIMBO-I_CLIPS+1)
538      {
539          strcpy(buf, "pickups/");
540 
541          defformatstring(widn)("modmdlpickup%d", i-3);
542 
543          if (identexists(widn))
544          strcat(buf, getalias(widn));
545          else
546          strcat(buf, entmdlnames[i]);
547 
548          model *mdl = loadmodel(buf);
549 
550          if(dynshadow && mdl) mdl->genshadows(8.0f, 2.0f);
551      }
552      static const char *bouncemdlnames[] = { "misc/gib01", "misc/gib02", "misc/gib03", "weapons/grenade/static" };
553      loopi(sizeof(bouncemdlnames)/sizeof(bouncemdlnames[0]))
554      {
555          model *mdl = NULL;
556          defformatstring(widn)("modmdlbounce%d", i);
557 
558          if (identexists(widn))
559          mdl = loadmodel(getalias(widn));
560          else
561          mdl = loadmodel(bouncemdlnames[i]);
562 
563          if(dynshadow && mdl) mdl->genshadows(8.0f, 2.0f);
564      }
565 }
566 
preload_mapmodels(bool trydl)567 void preload_mapmodels(bool trydl)
568 {
569     loopv(ents)
570     {
571         entity &e = ents[i];
572         if(e.type!=MAPMODEL || !mapmodels.inrange(e.attr2)) continue;
573         loadmodel(NULL, e.attr2, trydl);
574         if(e.attr4) lookuptexture(e.attr4, notexture, trydl);
575     }
576 }
577 
renderhboxpart(playerent * d,vec top,vec bottom,vec up)578 inline void renderhboxpart(playerent *d, vec top, vec bottom, vec up)
579 {
580     if(d->state==CS_ALIVE && d->head.x >= 0)
581     {
582         glBegin(GL_LINE_LOOP);
583         loopi(8)
584         {
585             vec pos(camright);
586             pos.rotate(2*M_PI*i/8.0f, camdir).mul(HEADSIZE).add(d->head);
587             glVertex3fv(pos.v);
588         }
589         glEnd();
590 
591         glBegin(GL_LINES);
592         glVertex3fv(bottom.v);
593         glVertex3fv(d->head.v);
594         glEnd();
595     }
596 
597     vec spoke;
598     spoke.orthogonal(up);
599     spoke.normalize().mul(d->radius);
600 
601     glBegin(GL_LINE_LOOP);
602     loopi(8)
603     {
604         vec pos(spoke);
605         pos.rotate(2*M_PI*i/8.0f, up).add(top);
606         glVertex3fv(pos.v);
607     }
608     glEnd();
609     glBegin(GL_LINE_LOOP);
610     loopi(8)
611     {
612         vec pos(spoke);
613         pos.rotate(2*M_PI*i/8.0f, up).add(bottom);
614         glVertex3fv(pos.v);
615     }
616     glEnd();
617     glBegin(GL_LINES);
618     loopi(8)
619     {
620         vec pos(spoke);
621         pos.rotate(2*M_PI*i/8.0f, up).add(bottom);
622         glVertex3fv(pos.v);
623         pos.sub(bottom).add(top);
624         glVertex3fv(pos.v);
625     }
626     glEnd();
627 }
628 
renderclient(playerent * d,const char * mdlname,const char * vwepname,int tex)629 void renderclient(playerent *d, const char *mdlname, const char *vwepname, int tex)
630 {
631     int varseed = (int)(size_t)d;
632     int anim = ANIM_IDLE|ANIM_LOOP;
633     float speed = 0.0;
634     vec o(d->o);
635     o.z -= d->eyeheight;
636     int basetime = -((int)(size_t)d&0xFFF);
637     if(d->state==CS_DEAD)
638     {
639         if(d==player1 && d->allowmove()) return;
640         loopv(bounceents) if(bounceents[i]->bouncetype==BT_GIB && bounceents[i]->owner==d) return;
641         d->pitch = 0.1f;
642         anim = ANIM_DEATH;
643         varseed += d->lastpain;
644         basetime = d->lastpain;
645         int t = lastmillis-d->lastpain;
646         if(t<0 || t>20000) return;
647         if(t>2000)
648         {
649             anim = ANIM_LYING_DEAD|ANIM_NOINTERP|ANIM_LOOP;
650             basetime += 2000;
651             t -= 2000;
652             o.z -= t*t/10000000000.0f*t;
653         }
654     }
655     else if(d->state==CS_EDITING)                   { anim = ANIM_JUMP|ANIM_END; }
656     else if(d->state==CS_LAGGED)                    { anim = ANIM_SALUTE|ANIM_LOOP|ANIM_TRANSLUCENT; }
657     else if(lastmillis-d->lastpain<300)             { anim = d->crouching ? ANIM_CROUCH_PAIN : ANIM_PAIN; speed = 300.0f/4; varseed += d->lastpain; basetime = d->lastpain; }
658 //     else if(!d->onfloor && d->timeinair>50)         { anim = ANIM_JUMP|ANIM_END; }
659     else if(!d->onfloor && d->timeinair>50)         { anim = (d->crouching ? ANIM_CROUCH_WALK : ANIM_JUMP)|ANIM_END; }
660     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; }
661     else if(!d->move && !d->strafe)                 { anim = (d->crouching ? ANIM_CROUCH_IDLE : ANIM_IDLE)|ANIM_LOOP; }
662     else                                            { anim = (d->crouching ? ANIM_CROUCH_WALK : ANIM_RUN)|ANIM_LOOP; speed = 1860/d->maxspeed; }
663     if(d->move < 0) anim |= ANIM_REVERSE;
664     modelattach a[3];
665     int numattach = 0;
666     if(vwepname)
667     {
668         a[numattach].name = vwepname;
669         a[numattach].tag = "tag_weapon";
670         numattach++;
671     }
672 
673     if(!stenciling && !reflecting && !refracting)
674     {
675         if(d->weaponsel==d->lastattackweapon && lastmillis-d->lastaction < d->weaponsel->flashtime())
676             anim |= ANIM_PARTICLE;
677         if(d != player1 && d->state==CS_ALIVE)
678         {
679             d->head = vec(-1, -1, -1);
680             a[numattach].tag = "tag_head";
681             a[numattach].pos = &d->head;
682             numattach++;
683         }
684     }
685     if(player1->isspectating() && d->clientnum == player1->followplayercn && player1->spectatemode == SM_FOLLOW3RD_TRANSPARENT)
686     {
687         anim |= ANIM_TRANSLUCENT; // see through followed player
688         if(stenciling) return;
689     }
690     rendermodel(mdlname, anim|ANIM_DYNALLOC, tex, 1.5f, o, d->yaw+90, d->pitch/4, speed, basetime, d, a);
691     if(!stenciling && !reflecting && !refracting)
692     {
693         if(isteam(player1->team, d->team)) renderaboveheadicon(d);
694     }
695 }
696 
697 VARP(teamdisplaymode, 0, 1, 2);
698 
699 #define SKINBASE "packages/models/playermodels"
700 VARP(hidecustomskins, 0, 0, 2);
701 static vector<char *> playerskinlist;
702 
getclientskin(const char * name,const char * suf)703 const char *getclientskin(const char *name, const char *suf)
704 {
705     static string tmp;
706     int suflen = (int)strlen(suf), namelen = (int)strlen(name);
707     const char *s, *r = NULL;
708     loopv(playerskinlist)
709     {
710         s = playerskinlist[i];
711         int sl = (int)strlen(s) - suflen;
712         if(sl > 0 && !strcmp(s + sl, suf))
713         {
714             if(namelen == sl && !strncmp(name, s, namelen)) return s; // exact match
715             if(s[sl - 1] == '_')
716             {
717                 copystring(tmp, s);
718                 tmp[sl - 1] = '\0';
719                 if(strstr(name, tmp)) r = s; // partial match
720             }
721         }
722     }
723     return r;
724 }
725 
updateclientname(playerent * d)726 void updateclientname(playerent *d)
727 {
728     static bool gotlist = false;
729     if(!gotlist) listfiles(SKINBASE "/custom", "jpg", playerskinlist);
730     gotlist = true;
731     if(!d || !playerskinlist.length()) return;
732     d->skin_noteam = getclientskin(d->name, "_ffa");
733     d->skin_cla = getclientskin(d->name, "_cla");
734     d->skin_rvsf = getclientskin(d->name, "_rvsf");
735 }
736 
renderclient(playerent * d)737 void renderclient(playerent *d)
738 {
739     if(!d) return;
740     int team = team_base(d->team);
741     const char *cs = NULL, *skinbase = SKINBASE, *teamname = team_basestring(team);
742     int skinid = 1 + d->skin();
743     string skin;
744     if(hidecustomskins == 0 || (hidecustomskins == 1 && !m_teammode))
745     {
746         cs = team ? d->skin_rvsf : d->skin_cla;
747         if(!m_teammode && d->skin_noteam) cs = d->skin_noteam;
748     }
749     if(cs)
750         formatstring(skin)("%s/custom/%s.jpg", skinbase, cs);
751     else
752     {
753         if(!m_teammode || !teamdisplaymode) formatstring(skin)("%s/%s/%02i.jpg", skinbase, teamname, skinid);
754         else switch(teamdisplaymode)
755         {
756             case 1: formatstring(skin)("%s/%s/%02i_%svest.jpg", skinbase, teamname, skinid, team ? "blue" : "red"); break;
757             case 2: default: formatstring(skin)("%s/%s/%s.jpg", skinbase, teamname, team ? "blue" : "red"); break;
758         }
759     }
760     string vwep;
761     defformatstring(widn)("modmdlvwep%d", d->weaponsel->type);
762     if(d->weaponsel) formatstring(vwep)("weapons/%s/world", identexists(widn)?getalias(widn):d->weaponsel->info.modelname);
763     else vwep[0] = 0;
764     renderclient(d, "playermodels", vwep[0] ? vwep : NULL, -(int)textureload(skin)->id);
765 }
766 
renderclients()767 void renderclients()
768 {
769     playerent *d;
770     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);
771     if(player1->state==CS_DEAD || (reflecting && !refracting)) renderclient(player1);
772 }
773