]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/csqcmodel_hooks.qc
sv_forceplayercolors
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / csqcmodel_hooks.qc
1 .float isplayermodel;
2
3 // FEATURE: LOD
4 .float lodmodelindex0;
5 .float lodmodelindex1;
6 .float lodmodelindex2;
7 void CSQCPlayer_LOD_Apply(void)
8 {
9         // LOD model loading
10         if(self.lodmodelindex0 != self.modelindex)
11         {
12                 string modelname = self.model;
13                 string s;
14
15                 if(!fexists(modelname))
16                 {
17                         print(sprintf(_("Trying to use non existing model %s. "), modelname));
18                         modelname = cvar_defstring("_cl_playermodel");
19                         print(sprintf(_("Reverted to %s.\n"), modelname));
20                 }
21
22                 // set modelindex
23                 self.lodmodelindex0 = self.modelindex;
24                 self.lodmodelindex1 = self.modelindex;
25                 self.lodmodelindex2 = self.modelindex;
26
27                 // FIXME: this only supports 3-letter extensions
28                 s = strcat(substring(modelname, 0, strlen(modelname)-4), "_lod1", substring(modelname, -4, 4));
29                 if(fexists(s))
30                 {
31                         precache_model(s);
32                         setmodel(self, s);
33                         if(self.modelindex)
34                                 self.lodmodelindex1 = self.modelindex;
35                 }
36
37                 s = strcat(substring(modelname, 0, strlen(modelname)-4), "_lod2", substring(modelname, -4, 4));
38                 if(fexists(s))
39                 {
40                         precache_model(s);
41                         setmodel(self, s);
42                         if(self.modelindex)
43                                 self.lodmodelindex2 = self.modelindex;
44                 }
45
46                 setmodel(self, modelname); // make everything normal again
47         }
48
49         // apply LOD
50         if(autocvar_cl_playerdetailreduction <= 0)
51         {
52                 if(autocvar_cl_playerdetailreduction <= -2)
53                         self.modelindex = self.lodmodelindex2;
54                 else if(autocvar_cl_playerdetailreduction <= -1)
55                         self.modelindex = self.lodmodelindex1;
56                 else
57                         self.modelindex = self.lodmodelindex0;
58         }
59         else
60         {
61                 float distance = vlen(self.origin - other.origin);
62                 float f = (distance + 100.0) * autocvar_cl_playerdetailreduction;
63                 f *= 1.0 / bound(0.01, view_quality, 1);
64                 if(f > autocvar_cl_loddistance2)
65                         self.modelindex = self.lodmodelindex2;
66                 else if(f > autocvar_cl_loddistance1)
67                         self.modelindex = self.lodmodelindex1;
68                 else
69                         self.modelindex = self.lodmodelindex0;
70         }
71 }
72
73 // FEATURE: forcemodel (MUST be called BEFORE LOD!)
74 string forceplayermodels_model;
75 float forceplayermodels_modelindex;
76 float forceplayermodels_skin;
77 float forceplayermodels_attempted;
78 .string forceplayermodels_savemodel;
79 .float forceplayermodels_savemodelindex;
80 .float forceplayermodels_saveskin;
81 .float forceplayermodels_savecolormap;
82 void CSQCPlayer_ForceModel_PreUpdate(void)
83 {
84         self.model = self.forceplayermodels_savemodel;
85         self.modelindex = self.forceplayermodels_savemodelindex;
86         self.skin = self.forceplayermodels_saveskin;
87         self.colormap = self.forceplayermodels_savecolormap;
88 }
89 void CSQCPlayer_ForceModel_PostUpdate(void)
90 {
91         self.forceplayermodels_savemodel = self.model;
92         self.forceplayermodels_savemodelindex = self.modelindex;
93         self.forceplayermodels_saveskin = self.skin;
94         self.forceplayermodels_savecolormap = self.colormap;
95 }
96 void CSQCPlayer_ForceModel_Apply(float islocalplayer)
97 {
98         // first, try finding it from the server
99
100         if(self.forceplayermodels_savemodelindex && self.forceplayermodels_savemodel != "null")
101         {
102                 if(islocalplayer)
103                 {
104                         // trust server's idea of "own player model"
105                         forceplayermodels_model = self.model;
106                         forceplayermodels_modelindex = self.modelindex;
107                         forceplayermodels_skin = self.skin;
108                         forceplayermodels_attempted = 1;
109                 }
110         }
111
112         // forcemodel finding
113         if(!forceplayermodels_attempted)
114         {
115                 // only if this failed, find it out on our own
116                 entity e;
117                 e = spawn();
118                 setmodel(e, autocvar__cl_playermodel); // this is harmless, see below
119                 forceplayermodels_model = e.model;
120                 forceplayermodels_modelindex = e.modelindex;
121                 forceplayermodels_skin = autocvar__cl_playerskin;
122                 forceplayermodels_attempted = 1;
123                 remove(e);
124         }
125
126         // apply it
127         if(autocvar_cl_forceplayermodels && forceplayermodels_modelindex)
128         {
129                 self.model = forceplayermodels_model;
130                 self.modelindex = forceplayermodels_modelindex;
131                 self.skin = forceplayermodels_skin;
132         }
133         else
134         {
135                 self.model = self.forceplayermodels_savemodel;
136                 self.modelindex = self.forceplayermodels_savemodelindex;
137                 self.skin = self.forceplayermodels_saveskin;
138         }
139
140         // forceplayercolors too
141         if(!teamplay)
142                 if(autocvar_cl_forceplayercolors)
143                         self.colormap = player_localnum + 1;
144 }
145
146 // FEATURE: fallback frames
147 .float csqcmodel_saveframe;
148 .float csqcmodel_saveframe2;
149 .float csqcmodel_saveframe3;
150 .float csqcmodel_saveframe4;
151 .float csqcmodel_framecount;
152 void CSQCPlayer_FallbackFrame_PreUpdate(void)
153 {
154         self.frame = self.csqcmodel_saveframe;
155         self.frame2 = self.csqcmodel_saveframe2;
156         self.frame3 = self.csqcmodel_saveframe3;
157         self.frame4 = self.csqcmodel_saveframe4;
158 }
159 void CSQCPlayer_FallbackFrame_PostUpdate(float isnew)
160 {
161         self.csqcmodel_saveframe = self.frame;
162         self.csqcmodel_saveframe2 = self.frame2;
163         self.csqcmodel_saveframe3 = self.frame3;
164         self.csqcmodel_saveframe4 = self.frame4;
165
166         // hack for death animations: set their frametime to zero in case a
167         // player "pops in"
168         if(isnew)
169         {
170 #define FIX_FRAMETIME(f,ft) \
171                 switch(self.f) \
172                 { \
173                         case 0: \
174                         case 1: \
175                                 self.ft = 0; \
176                                 break; \
177                 }
178                 FIX_FRAMETIME(frame, frame1time)
179                 FIX_FRAMETIME(frame2, frame2time)
180                 FIX_FRAMETIME(frame3, frame3time)
181                 FIX_FRAMETIME(frame4, frame4time)
182         }
183 }
184 float CSQCPlayer_FallbackFrame(float f)
185 {
186         if(frameduration(self.modelindex, f) > 0)
187                 return f; // goooooood
188         switch(f)
189         {
190                 case 23: return 11; // anim_melee -> anim_shoot
191                 case 24: return 4; // anim_duckwalkbackwards -> anim_duckwalk
192                 case 25: return 4; // anim_duckwalkstrafeleft -> anim_duckwalk
193                 case 26: return 4; // anim_duckwalkstraferight -> anim_duckwalk
194                 case 27: return 4; // anim_duckwalkforwardright -> anim_duckwalk
195                 case 28: return 4; // anim_duckwalkforwardleft -> anim_duckwalk
196                 case 29: return 4; // anim_duckwalkbackright -> anim_duckwalk
197                 case 30: return 4; // anim_duckwalkbackleft -> anim_duckwalk
198         }
199         print(sprintf("Frame %d missing in model %s, and we have no fallback - FAIL!\n", f, self.model));
200         return f;
201 }
202 void CSQCPlayer_FallbackFrame_Apply(void)
203 {
204         self.frame = CSQCPlayer_FallbackFrame(self.frame);
205         self.frame2 = CSQCPlayer_FallbackFrame(self.frame2);
206         self.frame3 = CSQCPlayer_FallbackFrame(self.frame3);
207         self.frame4 = CSQCPlayer_FallbackFrame(self.frame4);
208 }
209
210 // FEATURE: auto glowmod
211 .vector glowmod;
212 void CSQCPlayer_GlowMod_Apply(void)
213 {
214         if(self.colormap > 0)
215                 self.glowmod = colormapPaletteColor(((self.colormap >= 1024) ? self.colormap : stof(getplayerkeyvalue(self.colormap - 1, "colors"))) & 0x0F, TRUE) * 2;
216         else
217                 self.glowmod = '1 1 1';
218 }
219
220 // FEATURE: auto tag_index
221 .entity tag_entity;
222 .float tag_entity_lastmodelindex;
223 .float tag_index;
224 void CSQCModel_AutoTagIndex_Apply(void)
225 {
226         if(self.tag_entity && wasfreed(self.tag_entity))
227                 self.tag_entity = world;
228
229         if(self.tag_networkentity)
230         {
231                 // we are ATTACHED!
232                 float changed = 0;
233                 if(self.tag_entity.entnum != self.tag_networkentity)
234                 {
235                         self.tag_entity = findfloat(world, entnum, self.tag_networkentity);
236                         changed = 1;
237                 }
238                 if(self.tag_entity.modelindex != self.tag_entity_lastmodelindex)
239                 {
240                         self.tag_entity_lastmodelindex = self.tag_entity.modelindex;
241                         changed = 1;
242                 }
243                 if(changed)
244                 {
245                         if(self.tag_entity)
246                         {
247                                 // the best part is: IT EXISTS
248                                 if(substring(self.model, 0, 17) == "models/weapons/v_")
249                                         if(substring(self.tag_entity.model, 0, 17) == "models/weapons/h_")
250                                         {
251                                                 self.tag_index = gettagindex(self.tag_entity, "weapon");
252                                                 if(!self.tag_index)
253                                                         self.tag_index = gettagindex(self.tag_entity, "tag_weapon");
254                                                 if(!self.tag_index)
255                                                 {
256                                                         // we need to prevent this from 'appening
257                                                         self.tag_entity = world;
258                                                         self.drawmask = 0;
259                                                         dprint("h_ model lacks weapon attachment, but v_ model is attached to it\n");
260                                                 }
261                                         }
262
263                                 if(substring(self.model, 0, 17) == "models/weapons/v_")
264                                         if(substring(self.tag_entity.model, 0, 14) == "models/player/")
265                                         {
266                                                 self.tag_index = gettagindex(self.tag_entity, "tag_weapon");
267                                                 if(!self.tag_index)
268                                                         self.tag_index = gettagindex(self.tag_entity, "bip01 r hand");
269                                         }
270
271                                 if(substring(self.tag_entity.model, 0, 17) == "models/weapons/v_")
272                                 {
273                                         self.tag_index = gettagindex(self.tag_entity, "shot");
274                                         if(!self.tag_index)
275                                                 self.tag_index = gettagindex(self.tag_entity, "tag_shot");
276                                 }
277                         }
278                         else
279                         {
280                                 // damn, see you next frame
281                                 self.drawmask = 0;
282                         }
283                 }
284         }
285 }
286
287 // FEATURE: EF_NODRAW workalike
288 float EF_BRIGHTFIELD    = 1;
289 float EF_BRIGHTLIGHT    = 4;
290 float EF_DIMLIGHT       = 8;
291 float EF_DOUBLESIDED = 32768;
292 float EF_NOSELFSHADOW = 65536;
293 float EF_DYNAMICMODELLIGHT = 131072;
294 float MF_ROCKET  =   1; // leave a trail
295 float MF_GRENADE =   2; // leave a trail
296 float MF_GIB     =   4; // leave a trail
297 float MF_ROTATE  =   8; // rotate (bonus items)
298 float MF_TRACER  =  16; // green split trail
299 float MF_ZOMGIB  =  32; // small blood trail
300 float MF_TRACER2 =  64; // orange split trail
301 float MF_TRACER3 = 128; // purple trail
302 .float csqcmodel_effects;
303 .float csqcmodel_modelflags;
304 void CSQCModel_Effects_PreUpdate(void)
305 {
306         self.effects = self.csqcmodel_effects;
307         self.modelflags = self.csqcmodel_modelflags;
308 }
309 void CSQCModel_Effects_PostUpdate(void)
310 {
311         self.csqcmodel_effects = self.effects;
312         self.csqcmodel_modelflags = self.modelflags;
313         self.effects = 0;
314         self.modelflags = 0;
315         if(self.csqcmodel_teleported)
316                 Projectile_ResetTrail(self.origin);
317 }
318 void CSQCModel_Effects_Apply(void)
319 {
320         float eff = self.csqcmodel_effects;
321         eff &~= CSQCMODEL_EF_INVISIBLE;
322
323         self.renderflags &~= (RF_DEPTHHACK | RF_ADDITIVE | RF_FULLBRIGHT | EF_NOSHADOW | RF_USEAXIS);
324         self.effects = 0;
325         self.traileffect = 0;
326                         
327         if(eff & EF_BRIGHTFIELD)
328                 self.traileffect = particleeffectnum("TR_NEXUIZPLASMA");
329         // ignoring EF_MUZZLEFLASH
330         if(eff & EF_BRIGHTLIGHT)
331                 adddynamiclight(self.origin, 400, '3 3 3');
332         if(eff & EF_DIMLIGHT)
333                 adddynamiclight(self.origin, 200, '1.5 1.5 1.5');
334         if((eff & EF_NODRAW) || (self.csqcmodel_effects & CSQCMODEL_EF_INVISIBLE) || (self.alpha < 0))
335                 self.drawmask = 0;
336         if(eff & EF_ADDITIVE)
337                 self.renderflags |= RF_ADDITIVE;
338         if(eff & EF_BLUE)
339                 adddynamiclight(self.origin, 200, '0.15 0.15 1.5');
340         if(eff & EF_RED)
341                 adddynamiclight(self.origin, 200, '1.5 0.15 0.15');
342         // ignoring EF_NOGUNBOB
343         if(eff & EF_FULLBRIGHT)
344                 self.renderflags |= RF_FULLBRIGHT;
345         if(eff & EF_FLAME)
346                 pointparticles(particleeffectnum("EF_FLAME"), self.origin, '0 0 0', bound(0, frametime, 0.1));
347         if(eff & EF_STARDUST)
348                 pointparticles(particleeffectnum("EF_STARDUST"), self.origin, '0 0 0', bound(0, frametime, 0.1));
349         if(eff & EF_NOSHADOW)
350                 self.renderflags |= RF_NOSHADOW;
351         if(eff & EF_NODEPTHTEST)
352                 self.renderflags |= RF_DEPTHHACK;
353         // ignoring EF_SELECTABLE
354         if(eff & EF_DOUBLESIDED)
355                 self.effects |= EF_DOUBLESIDED;
356         if(eff & EF_NOSELFSHADOW)
357                 self.effects |= EF_NOSELFSHADOW;
358         if(eff & EF_DYNAMICMODELLIGHT)
359                 self.renderflags |= RF_DYNAMICMODELLIGHT;
360         // ignoring EF_UNUSED18, EF_UNUSED19, EF_RESTARTANIM_BIT, EF_TELEPORT_BIT, EF_LOWPRECISION
361         if(self.csqcmodel_modelflags & MF_ROCKET)
362                 self.traileffect = particleeffectnum("TR_ROCKET");
363         if(self.csqcmodel_modelflags & MF_GRENADE)
364                 self.traileffect = particleeffectnum("TR_GRENADE");
365         if(self.csqcmodel_modelflags & MF_GIB)
366                 self.traileffect = particleeffectnum("TR_BLOOD");
367         if(self.csqcmodel_modelflags & MF_ROTATE)
368         {
369                 self.renderflags |= RF_USEAXIS;
370                 makevectors(self.angles + '0 100 0' * fmod(time, 3.6));
371         }
372         if(self.csqcmodel_modelflags & MF_TRACER)
373                 self.traileffect = particleeffectnum("TR_WIZSPIKE");
374         if(self.csqcmodel_modelflags & MF_ZOMGIB)
375                 self.traileffect = particleeffectnum("TR_SLIGHTBLOOD");
376         if(self.csqcmodel_modelflags & MF_TRACER2)
377                 self.traileffect = particleeffectnum("TR_KNIGHTSPIKE");
378         if(self.csqcmodel_modelflags & MF_TRACER3)
379                 self.traileffect = particleeffectnum("TR_VORESPIKE");
380
381         if(self.drawmask)
382                 Projectile_DrawTrail(self.origin);
383         else
384                 Projectile_ResetTrail(self.origin);
385 }
386
387 // general functions
388 void CSQCModel_Hook_PreDraw(float isplayer, float islocalplayer)
389 {
390         if(!self.modelindex || self.model == "null")
391         {
392                 self.drawmask = 0;
393                 return;
394         }
395         else
396                 self.drawmask = MASK_NORMAL;
397
398         if(self.isplayermodel) // this checks if it's a player MODEL!
399         {
400                 CSQCPlayer_GlowMod_Apply();
401                 CSQCPlayer_ForceModel_Apply(islocalplayer);
402                 CSQCPlayer_LOD_Apply();
403                 CSQCPlayer_FallbackFrame_Apply();
404         }
405
406         if(!isplayer) // this checks if it's a player SLOT!
407                 CSQCModel_AutoTagIndex_Apply();
408
409         CSQCModel_Effects_Apply();
410 }
411
412 void CSQCModel_Hook_PreUpdate(float isnew, float isplayer, float islocalplayer)
413 {
414         // revert to values from server
415         CSQCModel_Effects_PreUpdate();
416         if(self.isplayermodel)
417         {
418                 CSQCPlayer_FallbackFrame_PreUpdate();
419                 CSQCPlayer_ForceModel_PreUpdate();
420         }
421 }
422
423 void CSQCModel_Hook_PostUpdate(float isnew, float isplayer, float islocalplayer)
424 {
425         // is it a player model? (shared state)
426         self.isplayermodel = (substring(self.model, 0, 14) == "models/player/");
427
428         // save values set by server
429         if(self.isplayermodel)
430         {
431                 CSQCPlayer_ForceModel_PostUpdate();
432                 CSQCPlayer_FallbackFrame_PostUpdate(isnew);
433         }
434         CSQCModel_Effects_PostUpdate();
435 }