adding special port rotation for boot peers
[SauerbratenRemote.git] / SauerbratenRemote / src / engine / md5.h
blob69c922fd5760affc07109368b666387a1bcbde53
1 struct md5;
3 md5 *loadingmd5 = NULL;
5 string md5dir;
7 struct md5joint
9 vec pos;
10 quat orient;
13 struct md5weight
15 int joint;
16 float bias;
17 vec pos;
18 };
20 struct md5vert
22 float u, v;
23 ushort start, count;
26 struct md5hierarchy
28 string name;
29 int parent, flags, start;
32 struct md5 : skelmodel
34 md5(const char *name) : skelmodel(name) {}
36 int type() const { return MDL_MD5; }
38 struct md5mesh : skelmesh
40 md5weight *weightinfo;
41 int numweights;
42 md5vert *vertinfo;
44 md5mesh() : weightinfo(NULL), numweights(0), vertinfo(NULL)
48 ~md5mesh()
50 cleanup();
53 void cleanup()
55 DELETEA(weightinfo);
56 DELETEA(vertinfo);
59 void buildverts(vector<md5joint> &joints)
61 loopi(numverts)
63 md5vert &v = vertinfo[i];
64 vec pos(0, 0, 0);
65 loopk(v.count)
67 md5weight &w = weightinfo[v.start+k];
68 md5joint &j = joints[w.joint];
69 pos.add(j.orient.rotate(w.pos).add(j.pos).mul(w.bias));
71 vert &vv = verts[i];
72 vv.pos = pos;
73 vv.u = v.u;
74 vv.v = v.v;
76 blendcombo c;
77 int sorted = 0;
78 loopj(v.count)
80 md5weight &w = weightinfo[v.start+j];
81 sorted = c.addweight(sorted, w.bias, w.joint);
83 c.finalize(sorted);
84 vv.blend = addblendcombo(c);
88 void buildnorms(bool areaweight = true)
90 loopi(numverts) verts[i].norm = vec(0, 0, 0);
91 loopi(numtris)
93 tri &t = tris[i];
94 vert &v1 = verts[t.vert[0]], &v2 = verts[t.vert[1]], &v3 = verts[t.vert[2]];
95 vec norm;
96 norm.cross(vec(v2.pos).sub(v1.pos), vec(v3.pos).sub(v1.pos));
97 if(!areaweight) norm.normalize();
98 v1.norm.add(norm);
99 v2.norm.add(norm);
100 v3.norm.add(norm);
102 loopi(numverts) verts[i].norm.normalize();
105 void load(FILE *f, char *buf, size_t bufsize)
107 md5weight w;
108 md5vert v;
109 tri t;
110 int index;
112 for(;;)
114 fgets(buf, bufsize, f);
115 if(buf[0]=='}' || feof(f)) break;
116 if(strstr(buf, "// meshes:"))
118 char *start = strchr(buf, ':')+1;
119 if(*start==' ') start++;
120 char *end = start + strlen(start)-1;
121 while(end >= start && isspace(*end)) end--;
122 name = newstring(start, end+1-start);
124 else if(strstr(buf, "shader"))
126 char *start = strchr(buf, '"'), *end = start ? strchr(start+1, '"') : NULL;
127 if(start && end)
129 char *texname = newstring(start+1, end-(start+1));
130 part *p = loadingmd5->parts.last();
131 p->initskins(notexture, notexture, group->meshes.length());
132 skin &s = p->skins.last();
133 s.tex = textureload(makerelpath(md5dir, texname), 0, true, false);
134 delete[] texname;
137 else if(sscanf(buf, " numverts %d", &numverts)==1)
139 numverts = max(numverts, 0);
140 if(numverts)
142 vertinfo = new md5vert[numverts];
143 verts = new vert[numverts];
146 else if(sscanf(buf, " numtris %d", &numtris)==1)
148 numtris = max(numtris, 0);
149 if(numtris) tris = new tri[numtris];
151 else if(sscanf(buf, " numweights %d", &numweights)==1)
153 numweights = max(numweights, 0);
154 if(numweights) weightinfo = new md5weight[numweights];
156 else if(sscanf(buf, " vert %d ( %f %f ) %hu %hu", &index, &v.u, &v.v, &v.start, &v.count)==5)
158 if(index>=0 && index<numverts) vertinfo[index] = v;
160 else if(sscanf(buf, " tri %d %hu %hu %hu", &index, &t.vert[0], &t.vert[1], &t.vert[2])==4)
162 if(index>=0 && index<numtris) tris[index] = t;
164 else if(sscanf(buf, " weight %d %d %f ( %f %f %f ) ", &index, &w.joint, &w.bias, &w.pos.x, &w.pos.y, &w.pos.z)==6)
166 w.pos.y = -w.pos.y;
167 if(index>=0 && index<numweights) weightinfo[index] = w;
173 struct md5meshgroup : skelmeshgroup
175 md5meshgroup()
179 bool loadmd5mesh(const char *filename)
181 FILE *f = openfile(filename, "r");
182 if(!f) return false;
184 char buf[512];
185 vector<md5joint> basejoints;
186 for(;;)
188 fgets(buf, sizeof(buf), f);
189 if(feof(f)) break;
190 int tmp;
191 if(sscanf(buf, " MD5Version %d", &tmp)==1)
193 if(tmp!=10) { fclose(f); return false; }
195 else if(sscanf(buf, " numJoints %d", &tmp)==1)
197 if(tmp<1) { fclose(f); return false; }
198 if(skel->numbones>0) continue;
199 skel->numbones = tmp;
200 skel->bones = new boneinfo[skel->numbones];
202 else if(sscanf(buf, " numMeshes %d", &tmp)==1)
204 if(tmp<1) { fclose(f); return false; }
206 else if(strstr(buf, "joints {"))
208 string name;
209 int parent;
210 md5joint j;
211 for(;;)
213 fgets(buf, sizeof(buf), f);
214 if(buf[0]=='}' || feof(f)) break;
215 if(sscanf(buf, " %s %d ( %f %f %f ) ( %f %f %f )",
216 name, &parent, &j.pos.x, &j.pos.y, &j.pos.z,
217 &j.orient.x, &j.orient.y, &j.orient.z)==8)
219 j.pos.y = -j.pos.y;
220 j.orient.x = -j.orient.x;
221 j.orient.z = -j.orient.z;
222 if(basejoints.length()<skel->numbones)
224 if(!skel->bones[basejoints.length()].name)
226 char *start = strchr(name, '"'), *end = start ? strchr(start+1, '"') : NULL;
227 skel->bones[basejoints.length()].name = start && end ? newstring(start+1, end-(start+1)) : newstring(name);
229 skel->bones[basejoints.length()].parent = parent;
231 j.orient.restorew();
232 basejoints.add(j);
235 if(basejoints.length()!=skel->numbones) { fclose(f); return false; }
237 else if(strstr(buf, "mesh {"))
239 md5mesh *m = new md5mesh;
240 m->group = this;
241 meshes.add(m);
242 m->load(f, buf, sizeof(buf));
243 if(!m->numtris || !m->numverts)
245 conoutf("empty mesh in %s", filename);
246 meshes.removeobj(m);
247 delete m;
252 if(skel->shared <= 1)
254 skel->linkchildren();
255 loopv(basejoints) skel->bones[i].base = dualquat(basejoints[i].orient, basejoints[i].pos);
258 loopv(meshes)
260 md5mesh &m = *(md5mesh *)meshes[i];
261 m.buildverts(basejoints);
262 m.buildnorms();
263 m.cleanup();
266 sortblendcombos();
268 fclose(f);
269 return true;
272 skelanimspec *loadmd5anim(const char *filename)
274 skelanimspec *sa = skel->findskelanim(filename);
275 if(sa) return sa;
277 FILE *f = openfile(filename, "r");
278 if(!f) return NULL;
280 vector<md5hierarchy> hierarchy;
281 vector<md5joint> basejoints;
282 int animdatalen = 0, animframes = 0;
283 float *animdata = NULL;
284 dualquat *animbones = NULL;
285 char buf[512];
286 for(;;)
288 fgets(buf, sizeof(buf), f);
289 if(feof(f)) break;
290 int tmp;
291 if(sscanf(buf, " MD5Version %d", &tmp)==1)
293 if(tmp!=10) { fclose(f); return NULL; }
295 else if(sscanf(buf, " numJoints %d", &tmp)==1)
297 if(tmp!=skel->numbones) { fclose(f); return NULL; }
299 else if(sscanf(buf, " numFrames %d", &animframes)==1)
301 if(animframes<1) { fclose(f); return NULL; }
303 else if(sscanf(buf, " frameRate %d", &tmp)==1);
304 else if(sscanf(buf, " numAnimatedComponents %d", &animdatalen)==1)
306 if(animdatalen<1) { fclose(f); return NULL; }
307 animdata = new float[animdatalen];
309 else if(strstr(buf, "bounds {"))
311 for(;;)
313 fgets(buf, sizeof(buf), f);
314 if(buf[0]=='}' || feof(f)) break;
317 else if(strstr(buf, "hierarchy {"))
319 for(;;)
321 fgets(buf, sizeof(buf), f);
322 if(buf[0]=='}' || feof(f)) break;
323 md5hierarchy h;
324 if(sscanf(buf, " %s %d %d %d", h.name, &h.parent, &h.flags, &h.start)==4)
325 hierarchy.add(h);
328 else if(strstr(buf, "baseframe {"))
330 for(;;)
332 fgets(buf, sizeof(buf), f);
333 if(buf[0]=='}' || feof(f)) break;
334 md5joint j;
335 if(sscanf(buf, " ( %f %f %f ) ( %f %f %f )", &j.pos.x, &j.pos.y, &j.pos.z, &j.orient.x, &j.orient.y, &j.orient.z)==6)
337 j.pos.y = -j.pos.y;
338 j.orient.x = -j.orient.x;
339 j.orient.z = -j.orient.z;
340 j.orient.restorew();
341 basejoints.add(j);
344 if(basejoints.length()!=skel->numbones) { fclose(f); return NULL; }
345 animbones = new dualquat[(skel->numframes+animframes)*skel->numbones];
346 if(skel->bones)
348 memcpy(animbones, skel->framebones, skel->numframes*skel->numbones*sizeof(dualquat));
349 delete[] skel->framebones;
351 skel->framebones = animbones;
352 animbones += skel->numframes*skel->numbones;
354 sa = &skel->addskelanim(filename);
355 sa->frame = skel->numframes;
356 sa->range = animframes;
358 skel->numframes += animframes;
360 else if(sscanf(buf, " frame %d", &tmp)==1)
362 loopi(animdatalen) fscanf(f, "%f", &animdata[i]);
363 dualquat *frame = &animbones[tmp*skel->numbones];
364 loopv(basejoints)
366 md5hierarchy &h = hierarchy[i];
367 md5joint j = basejoints[i];
368 float *jdata = &animdata[h.start];
369 if(h.flags)
371 if(h.flags&1) j.pos.x = *jdata++;
372 if(h.flags&2) j.pos.y = -*jdata++;
373 if(h.flags&4) j.pos.z = *jdata++;
374 if(h.flags&8) j.orient.x = -*jdata++;
375 if(h.flags&16) j.orient.y = *jdata++;
376 if(h.flags&32) j.orient.z = -*jdata++;
377 j.orient.restorew();
378 /*if(memcmp(&j, &basejoints[i], sizeof(j))) usedjoints[i] = 1; */
380 frame[i] = dualquat(j.orient, j.pos);
381 #if 0
382 if(h.parent<0) frame[i] = dualquat(j.orient, j.pos);
383 else (frame[i] = frame[h.parent]).mul(dualquat(j.orient, j.pos));
384 #endif
389 DELETEA(animdata);
390 fclose(f);
392 #if 0
393 vector<dualquat> invbase;
394 loopi(numbones)
396 dualquat &d = invbase.add(basebones[i]);
397 #if 1
398 d.translate(vec(translate).neg());
399 d.scale(1.0f/scale);
400 #endif
401 d.invert();
403 loopi(animframes)
405 dualquat *frame = &animbones[i*numbones];
406 loopj(numbones)
408 dualquat &d = frame[j];
409 #if 1
410 d.mul(invbase[j]);
411 #endif
412 d.scale(scale);
413 d.translate(translate);
414 #if 0
415 d.mul(invbase[j]);
416 #endif
419 #endif
421 return sa;
424 bool load(const char *meshfile)
426 name = newstring(meshfile);
428 if(!loadmd5mesh(meshfile)) return false;
430 return true;
434 void extendbb(int frame, vec &center, vec &radius, modelattach &a)
436 vec acenter, aradius;
437 a.m->boundbox(frame, acenter, aradius);
438 float margin = 2*max(aradius.x, max(aradius.y, aradius.z));
439 radius.x += margin;
440 radius.y += margin;
443 meshgroup *loadmeshes(char *name, va_list args)
445 md5meshgroup *group = new md5meshgroup;
446 group->shareskeleton(va_arg(args, char *));
447 if(!group->load(name)) { delete group; return NULL; }
448 return group;
451 bool loaddefaultparts()
453 skelpart &mdl = *new skelpart;
454 parts.add(&mdl);
455 mdl.model = this;
456 mdl.index = 0;
457 mdl.pitchscale = mdl.pitchoffset = mdl.pitchmin = mdl.pitchmax = 0;
458 const char *fname = loadname + strlen(loadname);
459 do --fname; while(fname >= loadname && *fname!='/' && *fname!='\\');
460 fname++;
461 #ifdef TC
462 s_sprintfd(meshname)("packages/plexus/models/%s/%s.md5mesh", loadname, fname);
463 if (!fileexists(meshname, "r")) {
464 s_sprintf(meshname)("packages/models/%s/%s.md5mesh", loadname, fname);
466 #else
467 s_sprintfd(meshname)("packages/models/%s/%s.md5mesh", loadname, fname);
468 #endif
469 mdl.meshes = sharemeshes(path(meshname));
470 if(!mdl.meshes) return false;
471 mdl.initanimparts();
472 mdl.initskins();
473 #ifdef TC
474 s_sprintfd(animname)("packages/plexus/models/%s/%s.md5anim", loadname, fname);
475 if (!fileexists(animname, "r")) {
476 s_sprintf(animname)("packages/plexus/models/%s/%s.md5anim", loadname, fname);
478 #else
479 s_sprintfd(animname)("packages/models/%s/%s.md5anim", loadname, fname);
480 #endif
481 ((md5meshgroup *)mdl.meshes)->loadmd5anim(path(animname));
482 return true;
485 bool load()
487 if(loaded) return true;
488 #ifdef TC
489 s_sprintf(md5dir)("packages/plexus/models/%s", loadname);
490 s_sprintfd(cfgname)("packages/plexus/models/%s/md5.cfg", loadname);
491 if (!fileexists(cfgname, "r")) {
492 s_sprintf(md5dir)("packages/models/%s", loadname);
493 s_sprintf(cfgname)("packages/models/%s/md5.cfg", loadname);
494 if (!fileexists(cfgname, "r")) {
495 return false;
498 #else
499 s_sprintf(md5dir)("packages/models/%s", loadname);
500 s_sprintfd(cfgname)("packages/models/%s/md5.cfg", loadname);
501 #endif
502 loadingmd5 = this;
503 persistidents = false;
504 if(execfile(cfgname) && parts.length()) // configured md5, will call the md5* commands below
506 persistidents = true;
507 loadingmd5 = NULL;
508 loopv(parts) if(!parts[i]->meshes) return false;
510 else if(!loaddefaultparts()) // md5 without configuration, try default tris and skin
512 persistidents = true;
513 loadingmd5 = NULL;
514 return false;
516 loadingmd5 = NULL;
517 loopv(parts)
519 skelpart *p = (skelpart *)parts[i];
520 p->endanimparts();
521 p->meshes = p->meshes->scaleverts(scale/4.0f, i ? vec(0, 0, 0) : vec(translate.x, translate.y, translate.z));
523 return loaded = true;
527 void md5load(char *meshfile, char *skelname)
529 if(!loadingmd5) { conoutf("not loading an md5"); return; }
530 s_sprintfd(filename)("%s/%s", md5dir, meshfile);
531 md5::skelpart &mdl = *new md5::skelpart;
532 loadingmd5->parts.add(&mdl);
533 mdl.model = loadingmd5;
534 mdl.index = loadingmd5->parts.length()-1;
535 mdl.pitchscale = mdl.pitchoffset = mdl.pitchmin = mdl.pitchmax = 0;
536 mdl.meshes = loadingmd5->sharemeshes(path(filename), skelname[0] ? skelname : NULL);
537 if(!mdl.meshes) conoutf("could not load %s", filename); // ignore failure
538 else
540 mdl.initanimparts();
541 mdl.initskins();
545 void md5tag(char *name, char *tagname)
547 if(!loadingmd5 || loadingmd5->parts.empty()) { conoutf("not loading an md5"); return; }
548 md5::part &mdl = *loadingmd5->parts.last();
549 int i = mdl.meshes ? ((md5::skelmeshgroup *)mdl.meshes)->skel->findbone(name) : -1;
550 if(i >= 0)
552 ((md5::skelmeshgroup *)mdl.meshes)->skel->addtag(tagname, i);
553 return;
555 conoutf("could not find bone %s for tag %s", name, tagname);
558 void md5pitch(char *name, float *pitchscale, float *pitchoffset, float *pitchmin, float *pitchmax)
560 if(!loadingmd5 || loadingmd5->parts.empty()) { conoutf("not loading an md5"); return; }
561 md5::part &mdl = *loadingmd5->parts.last();
563 if(name[0])
565 int i = mdl.meshes ? ((md5::skelmeshgroup *)mdl.meshes)->skel->findbone(name) : -1;
566 if(i>=0)
568 md5::boneinfo &b = ((md5::skelmeshgroup *)mdl.meshes)->skel->bones[i];
569 b.pitchscale = *pitchscale;
570 b.pitchoffset = *pitchoffset;
571 if(*pitchmin || *pitchmax)
573 b.pitchmin = *pitchmin;
574 b.pitchmax = *pitchmax;
576 else
578 b.pitchmin = -360*b.pitchscale;
579 b.pitchmax = 360*b.pitchscale;
581 return;
583 conoutf("could not find bone %s to pitch", name);
584 return;
587 mdl.pitchscale = *pitchscale;
588 mdl.pitchoffset = *pitchoffset;
589 if(*pitchmin || *pitchmax)
591 mdl.pitchmin = *pitchmin;
592 mdl.pitchmax = *pitchmax;
594 else
596 mdl.pitchmin = -360*mdl.pitchscale;
597 mdl.pitchmax = 360*mdl.pitchscale;
601 #define loopmd5meshes(meshname, m, body) \
602 if(!loadingmd5 || loadingmd5->parts.empty()) { conoutf("not loading an md5"); return; } \
603 md5::part &mdl = *loadingmd5->parts.last(); \
604 if(!mdl.meshes) return; \
605 loopv(mdl.meshes->meshes) \
607 md5::skelmesh &m = *(md5::skelmesh *)mdl.meshes->meshes[i]; \
608 if(!strcmp(meshname, "*") || (m.name && !strcmp(m.name, meshname))) \
610 body; \
614 #define loopmd5skins(meshname, s, body) loopmd5meshes(meshname, m, { md5::skin &s = mdl.skins[i]; body; })
616 void md5skin(char *meshname, char *tex, char *masks, float *envmapmax, float *envmapmin)
618 loopmd5skins(meshname, s,
619 s.tex = textureload(makerelpath(md5dir, tex), 0, true, false);
620 if(*masks)
622 s.masks = textureload(makerelpath(md5dir, masks, "<ffmask:25>"), 0, true, false);
623 s.envmapmax = *envmapmax;
624 s.envmapmin = *envmapmin;
629 void md5spec(char *meshname, int *percent)
631 float spec = 1.0f;
632 if(*percent>0) spec = *percent/100.0f;
633 else if(*percent<0) spec = 0.0f;
634 loopmd5skins(meshname, s, s.spec = spec);
637 void md5ambient(char *meshname, int *percent)
639 float ambient = 0.3f;
640 if(*percent>0) ambient = *percent/100.0f;
641 else if(*percent<0) ambient = 0.0f;
642 loopmd5skins(meshname, s, s.ambient = ambient);
645 void md5glow(char *meshname, int *percent)
647 float glow = 3.0f;
648 if(*percent>0) glow = *percent/100.0f;
649 else if(*percent<0) glow = 0.0f;
650 loopmd5skins(meshname, s, s.glow = glow);
653 void md5glare(char *meshname, float *specglare, float *glowglare)
655 loopmd5skins(meshname, s, { s.specglare = *specglare; s.glowglare = *glowglare; });
658 void md5alphatest(char *meshname, float *cutoff)
660 loopmd5skins(meshname, s, s.alphatest = max(0.0f, min(1.0f, *cutoff)));
663 void md5alphablend(char *meshname, int *blend)
665 loopmd5skins(meshname, s, s.alphablend = *blend!=0);
668 void md5envmap(char *meshname, char *envmap)
670 Texture *tex = cubemapload(envmap);
671 loopmd5skins(meshname, s, s.envmap = tex);
674 void md5bumpmap(char *meshname, char *normalmap, char *skin)
676 Texture *normalmaptex = NULL, *skintex = NULL;
677 normalmaptex = textureload(makerelpath(md5dir, normalmap, "<noff>"), 0, true, false);
678 if(skin[0]) skintex = textureload(makerelpath(md5dir, skin, "<noff>"), 0, true, false);
679 loopmd5skins(meshname, s, { s.unlittex = skintex; s.normalmap = normalmaptex; m.calctangents(); });
682 void md5translucent(char *meshname, float *translucency)
684 loopmd5skins(meshname, s, s.translucency = *translucency);
687 void md5fullbright(char *meshname, float *fullbright)
689 loopmd5skins(meshname, s, s.fullbright = *fullbright);
692 void md5shader(char *meshname, char *shader)
694 loopmd5skins(meshname, s, s.shader = lookupshaderbyname(shader));
697 void md5scroll(char *meshname, float *scrollu, float *scrollv)
699 loopmd5skins(meshname, s, { s.scrollu = *scrollu; s.scrollv = *scrollv; });
702 void md5anim(char *anim, char *animfile, float *speed, int *priority)
704 if(!loadingmd5 || loadingmd5->parts.empty()) { conoutf("not loading an md5"); return; }
706 vector<int> anims;
707 findanims(anim, anims);
708 if(anims.empty()) conoutf("could not find animation %s", anim);
709 else
711 s_sprintfd(filename)("%s/%s", md5dir, animfile);
712 md5::part *p = loadingmd5->parts.last();
713 md5::skelanimspec *sa = ((md5::md5meshgroup *)p->meshes)->loadmd5anim(path(filename));
714 if(!sa) conoutf("could not load md5anim file %s", filename);
715 else loopv(anims)
717 loadingmd5->parts.last()->setanim(p->numanimparts-1, anims[i], sa->frame, sa->range, *speed, *priority);
722 void md5animpart(char *maskstr)
724 if(!loadingmd5 || loadingmd5->parts.empty()) { conoutf("not loading an md5"); return; }
726 md5::skelpart *p = (md5::skelpart *)loadingmd5->parts.last();
728 vector<char *> bonestrs;
729 explodelist(maskstr, bonestrs);
730 vector<ushort> bonemask;
731 loopv(bonestrs)
733 char *bonestr = bonestrs[i];
734 int bone = p->meshes ? ((md5::skelmeshgroup *)p->meshes)->skel->findbone(bonestr[0]=='!' ? bonestr+1 : bonestr) : -1;
735 if(bone<0) { conoutf("could not find bone %s for anim part mask [%s]", bonestr, maskstr); bonestrs.deletecontentsa(); return; }
736 bonemask.add(bone | (bonestr[0]=='!' ? BONEMASK_NOT : 0));
738 bonestrs.deletecontentsa();
739 bonemask.sort(bonemaskcmp);
740 if(bonemask.length()) bonemask.add(BONEMASK_END);
742 if(!p->addanimpart(bonemask.getbuf())) conoutf("too many animation parts");
745 void md5link(int *parent, int *child, char *tagname)
747 if(!loadingmd5) { conoutf("not loading an md5"); return; }
748 if(!loadingmd5->parts.inrange(*parent) || !loadingmd5->parts.inrange(*child)) { conoutf("no models loaded to link"); return; }
749 if(!loadingmd5->parts[*parent]->link(loadingmd5->parts[*child], tagname)) conoutf("could not link model %s", loadingmd5->loadname);
752 void md5noclip(char *meshname, int *noclip)
754 loopmd5meshes(meshname, m, m.noclip = *noclip!=0);
757 COMMAND(md5load, "ss");
758 COMMAND(md5tag, "ss");
759 COMMAND(md5pitch, "sffff");
760 COMMAND(md5skin, "sssff");
761 COMMAND(md5spec, "si");
762 COMMAND(md5ambient, "si");
763 COMMAND(md5glow, "si");
764 COMMAND(md5glare, "sff");
765 COMMAND(md5alphatest, "sf");
766 COMMAND(md5alphablend, "si");
767 COMMAND(md5envmap, "ss");
768 COMMAND(md5bumpmap, "sss");
769 COMMAND(md5translucent, "sf");
770 COMMAND(md5fullbright, "sf");
771 COMMAND(md5shader, "ss");
772 COMMAND(md5scroll, "sff");
773 COMMAND(md5animpart, "s");
774 COMMAND(md5anim, "ssfi");
775 COMMAND(md5link, "iis");
776 COMMAND(md5noclip, "si");