]> git.xonotic.org Git - xonotic/darkplaces.git/blob - view.c
fix viewmodel crash
[xonotic/darkplaces.git] / view.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20 // view.c -- player eye positioning
21
22 #include "quakedef.h"
23
24 /*
25
26 The view is allowed to move slightly from it's true position for bobbing,
27 but if it exceeds 8 pixels linear distance (spherical, not box), the list of
28 entities sent from the server may not include everything in the pvs, especially
29 when crossing a water boudnary.
30
31 */
32
33 cvar_t  cl_rollspeed = {0, "cl_rollspeed", "200"};
34 cvar_t  cl_rollangle = {0, "cl_rollangle", "2.0"};
35
36 cvar_t  cl_bob = {0, "cl_bob","0.02"};
37 cvar_t  cl_bobcycle = {0, "cl_bobcycle","0.6"};
38 cvar_t  cl_bobup = {0, "cl_bobup","0.5"};
39
40 cvar_t  v_kicktime = {0, "v_kicktime", "0.5"};
41 cvar_t  v_kickroll = {0, "v_kickroll", "0.6"};
42 cvar_t  v_kickpitch = {0, "v_kickpitch", "0.6"};
43
44 cvar_t  v_iyaw_cycle = {0, "v_iyaw_cycle", "2"};
45 cvar_t  v_iroll_cycle = {0, "v_iroll_cycle", "0.5"};
46 cvar_t  v_ipitch_cycle = {0, "v_ipitch_cycle", "1"};
47 cvar_t  v_iyaw_level = {0, "v_iyaw_level", "0.3"};
48 cvar_t  v_iroll_level = {0, "v_iroll_level", "0.1"};
49 cvar_t  v_ipitch_level = {0, "v_ipitch_level", "0.3"};
50
51 cvar_t  v_idlescale = {0, "v_idlescale", "0"};
52
53 cvar_t  crosshair = {CVAR_SAVE, "crosshair", "0"};
54
55 cvar_t  v_centermove = {0, "v_centermove", "0.15"};
56 cvar_t  v_centerspeed = {0, "v_centerspeed","500"};
57
58 float   v_dmg_time, v_dmg_roll, v_dmg_pitch;
59
60
61 /*
62 ===============
63 V_CalcRoll
64
65 Used by view and sv_user
66 ===============
67 */
68 float V_CalcRoll (vec3_t angles, vec3_t velocity)
69 {
70         vec3_t  right;
71         float   sign;
72         float   side;
73         float   value;
74         
75         AngleVectors (angles, NULL, right, NULL);
76         side = DotProduct (velocity, right);
77         sign = side < 0 ? -1 : 1;
78         side = fabs(side);
79
80         value = cl_rollangle.value;
81
82         if (side < cl_rollspeed.value)
83                 side = side * value / cl_rollspeed.value;
84         else
85                 side = value;
86
87         return side*sign;
88
89 }
90
91 static float V_CalcBob (void)
92 {
93         double bob, cycle;
94
95         // LordHavoc: easy case
96         if (cl_bob.value == 0)
97                 return 0;
98         if (cl_bobcycle.value == 0)
99                 return 0;
100
101         // LordHavoc: FIXME: this code is *weird*, redesign it sometime
102         cycle = cl.time  / cl_bobcycle.value;
103         cycle -= (int) cycle;
104         if (cycle < cl_bobup.value)
105                 cycle = M_PI * cycle / cl_bobup.value;
106         else
107                 cycle = M_PI + M_PI*(cycle-cl_bobup.value)/(1.0 - cl_bobup.value);
108
109         // bob is proportional to velocity in the xy plane
110         // (don't count Z, or jumping messes it up)
111
112         bob = sqrt(cl.velocity[0]*cl.velocity[0] + cl.velocity[1]*cl.velocity[1]) * cl_bob.value;
113         bob = bob*0.3 + bob*0.7*sin(cycle);
114         bob = bound(-7, bob, 4);
115         return bob;
116
117 }
118
119 void V_StartPitchDrift (void)
120 {
121         if (cl.laststop == cl.time)
122                 return;         // something else is keeping it from drifting
123
124         if (cl.nodrift || !cl.pitchvel)
125         {
126                 cl.pitchvel = v_centerspeed.value;
127                 cl.nodrift = false;
128                 cl.driftmove = 0;
129         }
130 }
131
132 void V_StopPitchDrift (void)
133 {
134         cl.laststop = cl.time;
135         cl.nodrift = true;
136         cl.pitchvel = 0;
137 }
138
139 /*
140 ===============
141 V_DriftPitch
142
143 Moves the client pitch angle towards cl.idealpitch sent by the server.
144
145 If the user is adjusting pitch manually, either with lookup/lookdown,
146 mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped.
147
148 Drifting is enabled when the center view key is hit, mlook is released and
149 lookspring is non 0, or when
150 ===============
151 */
152 static void V_DriftPitch (void)
153 {
154         float           delta, move;
155
156         if (noclip_anglehack || !cl.onground || cls.demoplayback )
157         {
158                 cl.driftmove = 0;
159                 cl.pitchvel = 0;
160                 return;
161         }
162
163 // don't count small mouse motion
164         if (cl.nodrift)
165         {
166                 if ( fabs(cl.cmd.forwardmove) < cl_forwardspeed.value)
167                         cl.driftmove = 0;
168                 else
169                         cl.driftmove += cl.frametime;
170
171                 if ( cl.driftmove > v_centermove.value)
172                 {
173                         V_StartPitchDrift ();
174                 }
175                 return;
176         }
177
178         delta = cl.idealpitch - cl.viewangles[PITCH];
179
180         if (!delta)
181         {
182                 cl.pitchvel = 0;
183                 return;
184         }
185
186         move = cl.frametime * cl.pitchvel;
187         cl.pitchvel += cl.frametime * v_centerspeed.value;
188
189         if (delta > 0)
190         {
191                 if (move > delta)
192                 {
193                         cl.pitchvel = 0;
194                         move = delta;
195                 }
196                 cl.viewangles[PITCH] += move;
197         }
198         else if (delta < 0)
199         {
200                 if (move > -delta)
201                 {
202                         cl.pitchvel = 0;
203                         move = -delta;
204                 }
205                 cl.viewangles[PITCH] -= move;
206         }
207 }
208
209
210 /*
211 ==============================================================================
212
213                                                 SCREEN FLASHES
214
215 ==============================================================================
216 */
217
218
219 /*
220 ===============
221 V_ParseDamage
222 ===============
223 */
224 void V_ParseDamage (void)
225 {
226         int i, armor, blood;
227         vec3_t from, forward, right;
228         entity_t *ent;
229         float side, count;
230
231         armor = MSG_ReadByte ();
232         blood = MSG_ReadByte ();
233         for (i=0 ; i<3 ; i++)
234                 from[i] = MSG_ReadCoord ();
235
236         count = blood*0.5 + armor*0.5;
237         if (count < 10)
238                 count = 10;
239
240         cl.faceanimtime = cl.time + 0.2;                // put sbar face into pain frame
241
242         cl.cshifts[CSHIFT_DAMAGE].percent += 3*count;
243         if (cl.cshifts[CSHIFT_DAMAGE].percent < 0)
244                 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
245         if (cl.cshifts[CSHIFT_DAMAGE].percent > 150)
246                 cl.cshifts[CSHIFT_DAMAGE].percent = 150;
247
248         if (armor > blood)
249         {
250                 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 200;
251                 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 100;
252                 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 100;
253         }
254         else if (armor)
255         {
256                 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 220;
257                 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 50;
258                 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 50;
259         }
260         else
261         {
262                 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 255;
263                 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 0;
264                 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 0;
265         }
266
267 //
268 // calculate view angle kicks
269 //
270         ent = &cl_entities[cl.viewentity];
271
272         VectorSubtract (from, ent->render.origin, from);
273         VectorNormalize (from);
274
275         AngleVectors (ent->render.angles, forward, right, NULL);
276
277         side = DotProduct (from, right);
278         v_dmg_roll = count*side*v_kickroll.value;
279
280         side = DotProduct (from, forward);
281         v_dmg_pitch = count*side*v_kickpitch.value;
282
283         v_dmg_time = v_kicktime.value;
284 }
285
286 static cshift_t v_cshift;
287
288 /*
289 ==================
290 V_cshift_f
291 ==================
292 */
293 static void V_cshift_f (void)
294 {
295         v_cshift.destcolor[0] = atoi(Cmd_Argv(1));
296         v_cshift.destcolor[1] = atoi(Cmd_Argv(2));
297         v_cshift.destcolor[2] = atoi(Cmd_Argv(3));
298         v_cshift.percent = atoi(Cmd_Argv(4));
299 }
300
301
302 /*
303 ==================
304 V_BonusFlash_f
305
306 When you run over an item, the server sends this command
307 ==================
308 */
309 static void V_BonusFlash_f (void)
310 {
311         cl.cshifts[CSHIFT_BONUS].destcolor[0] = 215;
312         cl.cshifts[CSHIFT_BONUS].destcolor[1] = 186;
313         cl.cshifts[CSHIFT_BONUS].destcolor[2] = 69;
314         cl.cshifts[CSHIFT_BONUS].percent = 50;
315 }
316
317 /*
318 =============
319 V_UpdateBlends
320 =============
321 */
322 void V_UpdateBlends (void)
323 {
324         float   r, g, b, a, a2;
325         int             j;
326
327         if (cls.signon != SIGNONS)
328         {
329                 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
330                 cl.cshifts[CSHIFT_BONUS].percent = 0;
331                 cl.cshifts[CSHIFT_CONTENTS].percent = 0;
332                 cl.cshifts[CSHIFT_POWERUP].percent = 0;
333                 r_refdef.viewblend[0] = 0;
334                 r_refdef.viewblend[1] = 0;
335                 r_refdef.viewblend[2] = 0;
336                 r_refdef.viewblend[3] = 0;
337                 return;
338         }
339
340         // drop the damage value
341         cl.cshifts[CSHIFT_DAMAGE].percent -= (cl.time - cl.oldtime)*150;
342         if (cl.cshifts[CSHIFT_DAMAGE].percent <= 0)
343                 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
344
345         // drop the bonus value
346         cl.cshifts[CSHIFT_BONUS].percent -= (cl.time - cl.oldtime)*100;
347         if (cl.cshifts[CSHIFT_BONUS].percent <= 0)
348                 cl.cshifts[CSHIFT_BONUS].percent = 0;
349
350         // set contents color
351         switch (Mod_PointContents (r_refdef.vieworg, cl.worldmodel))
352         {
353         case CONTENTS_EMPTY:
354         case CONTENTS_SOLID:
355                 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = v_cshift.destcolor[0];
356                 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = v_cshift.destcolor[1];
357                 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = v_cshift.destcolor[2];
358                 cl.cshifts[CSHIFT_CONTENTS].percent = v_cshift.percent;
359                 break;
360         case CONTENTS_LAVA:
361                 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 255;
362                 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80;
363                 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 0;
364                 cl.cshifts[CSHIFT_CONTENTS].percent = 150 >> 1;
365                 break;
366         case CONTENTS_SLIME:
367                 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 0;
368                 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 25;
369                 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 5;
370                 cl.cshifts[CSHIFT_CONTENTS].percent = 150 >> 1;
371                 break;
372         default:
373                 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 130;
374                 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80;
375                 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 50;
376                 cl.cshifts[CSHIFT_CONTENTS].percent = 128 >> 1;
377         }
378
379         if (cl.items & IT_QUAD)
380         {
381                 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
382                 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 0;
383                 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 255;
384                 cl.cshifts[CSHIFT_POWERUP].percent = 30;
385         }
386         else if (cl.items & IT_SUIT)
387         {
388                 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
389                 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
390                 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
391                 cl.cshifts[CSHIFT_POWERUP].percent = 20;
392         }
393         else if (cl.items & IT_INVISIBILITY)
394         {
395                 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 100;
396                 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 100;
397                 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 100;
398                 cl.cshifts[CSHIFT_POWERUP].percent = 100;
399         }
400         else if (cl.items & IT_INVULNERABILITY)
401         {
402                 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 255;
403                 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
404                 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
405                 cl.cshifts[CSHIFT_POWERUP].percent = 30;
406         }
407         else
408                 cl.cshifts[CSHIFT_POWERUP].percent = 0;
409
410         // LordHavoc: fixed V_CalcBlend
411         r = 0;
412         g = 0;
413         b = 0;
414         a = 0;
415
416         for (j=0 ; j<NUM_CSHIFTS ; j++)
417         {
418                 a2 = cl.cshifts[j].percent * (1.0f / 255.0f);
419
420                 if (a2 < 0)
421                         continue;
422                 if (a2 > 1)
423                         a2 = 1;
424                 r += (cl.cshifts[j].destcolor[0]-r) * a2;
425                 g += (cl.cshifts[j].destcolor[1]-g) * a2;
426                 b += (cl.cshifts[j].destcolor[2]-b) * a2;
427                 a = 1 - (1 - a) * (1 - a2); // correct alpha multiply...  took a while to find it on the web
428         }
429         // saturate color (to avoid blending in black)
430         if (a)
431         {
432                 a2 = 1 / a;
433                 r *= a2;
434                 g *= a2;
435                 b *= a2;
436         }
437
438         r_refdef.viewblend[0] = bound(0, r * (1.0/255.0), 1);
439         r_refdef.viewblend[1] = bound(0, g * (1.0/255.0), 1);
440         r_refdef.viewblend[2] = bound(0, b * (1.0/255.0), 1);
441         r_refdef.viewblend[3] = bound(0, a              , 1);
442 }
443
444 /*
445 ==============================================================================
446
447                                                 VIEW RENDERING
448
449 ==============================================================================
450 */
451
452 /*
453 ==============
454 V_AddIdle
455
456 Idle swaying
457 ==============
458 */
459 static void V_AddIdle (float idle)
460 {
461         r_refdef.viewangles[ROLL] += idle * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
462         r_refdef.viewangles[PITCH] += idle * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
463         r_refdef.viewangles[YAW] += idle * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
464 }
465
466
467 /*
468 ==================
469 V_CalcRefdef
470
471 ==================
472 */
473 void V_CalcRefdef (void)
474 {
475         entity_t        *ent, *view;
476         vec3_t          forward;
477         vec3_t          angles;
478         float           bob;
479         float           side;
480
481         if (cls.state != ca_connected || cls.signon != SIGNONS)
482                 return;
483
484         // ent is the player model (visible when out of body)
485         ent = &cl_entities[cl.viewentity];
486         // view is the weapon model (only visible from inside body)
487         view = &cl.viewent;
488
489         V_DriftPitch ();
490
491         VectorCopy (cl.viewentorigin, r_refdef.vieworg);
492         if (!intimerefresh)
493                 VectorCopy (cl.viewangles, r_refdef.viewangles);
494
495         if (cl.intermission)
496         {
497                 view->render.model = NULL;
498                 VectorCopy (ent->render.angles, r_refdef.viewangles);
499                 V_AddIdle (1);
500         }
501         else if (chase_active.value)
502         {
503                 r_refdef.vieworg[2] += cl.viewheight;
504                 Chase_Update ();
505                 V_AddIdle (v_idlescale.value);
506         }
507         else
508         {
509                 side = V_CalcRoll (cl_entities[cl.viewentity].render.angles, cl.velocity);
510                 r_refdef.viewangles[ROLL] += side;
511
512                 if (v_dmg_time > 0)
513                 {
514                         r_refdef.viewangles[ROLL] += v_dmg_time/v_kicktime.value*v_dmg_roll;
515                         r_refdef.viewangles[PITCH] += v_dmg_time/v_kicktime.value*v_dmg_pitch;
516                         v_dmg_time -= cl.frametime;
517                 }
518
519                 if (cl.stats[STAT_HEALTH] <= 0)
520                         r_refdef.viewangles[ROLL] = 80; // dead view angle
521
522                 V_AddIdle (v_idlescale.value);
523
524                 // offsets
525                 angles[PITCH] = -ent->render.angles[PITCH];     // because entity pitches are actually backward
526                 angles[YAW] = ent->render.angles[YAW];
527                 angles[ROLL] = ent->render.angles[ROLL];
528
529                 AngleVectors (angles, forward, NULL, NULL);
530
531                 bob = V_CalcBob ();
532
533                 r_refdef.vieworg[2] += cl.viewheight + bob;
534
535                 // set up gun
536                 // (FIXME! this should be in cl_main.c with the other linking code, not view.c!)
537                 view->state_current.modelindex = cl.stats[STAT_WEAPON];
538                 view->state_current.frame = cl.stats[STAT_WEAPONFRAME];
539                 VectorCopy(r_refdef.vieworg, view->render.origin);
540                 //view->render.origin[0] = ent->render.origin[0] + bob * 0.4 * forward[0];
541                 //view->render.origin[1] = ent->render.origin[1] + bob * 0.4 * forward[1];
542                 //view->render.origin[2] = ent->render.origin[2] + bob * 0.4 * forward[2] + cl.viewheight + bob;
543                 view->render.angles[PITCH] = -r_refdef.viewangles[PITCH] - v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
544                 view->render.angles[YAW] = r_refdef.viewangles[YAW] - v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
545                 view->render.angles[ROLL] = r_refdef.viewangles[ROLL] - v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
546                 // FIXME: this setup code is somewhat evil (CL_LerpUpdate should be private?)
547                 CL_LerpUpdate(view);
548                 CL_BoundingBoxForEntity(&view->render);
549                 view->render.colormap = -1; // no special coloring
550                 view->render.alpha = ent->render.alpha; // LordHavoc: if the player is transparent, so is the gun
551                 view->render.effects = ent->render.effects;
552                 view->render.scale = 1.0 / 3.0;
553
554                 // LordHavoc: origin view kick added
555                 if (!intimerefresh)
556                 {
557                         VectorAdd(r_refdef.viewangles, cl.punchangle, r_refdef.viewangles);
558                         VectorAdd(r_refdef.vieworg, cl.punchvector, r_refdef.vieworg);
559                 }
560
561                 // link into render entities list
562                 if (r_refdef.numentities < r_refdef.maxentities && r_drawviewmodel.integer && !chase_active.integer && !envmap && r_drawentities.integer && !(cl.items & IT_INVISIBILITY) && cl.stats[STAT_HEALTH] > 0 && view->render.model != NULL)
563                         r_refdef.entities[r_refdef.numentities++] = &view->render;
564         }
565 }
566
567 //============================================================================
568
569 /*
570 =============
571 V_Init
572 =============
573 */
574 void V_Init (void)
575 {
576         Cmd_AddCommand ("v_cshift", V_cshift_f);
577         Cmd_AddCommand ("bf", V_BonusFlash_f);
578         Cmd_AddCommand ("centerview", V_StartPitchDrift);
579
580         Cvar_RegisterVariable (&v_centermove);
581         Cvar_RegisterVariable (&v_centerspeed);
582
583         Cvar_RegisterVariable (&v_iyaw_cycle);
584         Cvar_RegisterVariable (&v_iroll_cycle);
585         Cvar_RegisterVariable (&v_ipitch_cycle);
586         Cvar_RegisterVariable (&v_iyaw_level);
587         Cvar_RegisterVariable (&v_iroll_level);
588         Cvar_RegisterVariable (&v_ipitch_level);
589
590         Cvar_RegisterVariable (&v_idlescale);
591         Cvar_RegisterVariable (&crosshair);
592
593         Cvar_RegisterVariable (&cl_rollspeed);
594         Cvar_RegisterVariable (&cl_rollangle);
595         Cvar_RegisterVariable (&cl_bob);
596         Cvar_RegisterVariable (&cl_bobcycle);
597         Cvar_RegisterVariable (&cl_bobup);
598
599         Cvar_RegisterVariable (&v_kicktime);
600         Cvar_RegisterVariable (&v_kickroll);
601         Cvar_RegisterVariable (&v_kickpitch);
602 }
603