added in_pitch_min and in_pitch_max cvars to limit pitch (default: -90 to +90)
[divverent/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 //      if (cl.inwater)
82 //              value *= 6;
83
84         if (side < cl_rollspeed.value)
85                 side = side * value / cl_rollspeed.value;
86         else
87                 side = value;
88
89         return side*sign;
90
91 }
92
93 static float V_CalcBob (void)
94 {
95         double bob, cycle;
96
97         // LordHavoc: easy case
98         if (cl_bob.value == 0)
99                 return 0;
100         if (cl_bobcycle.value == 0)
101                 return 0;
102
103         // LordHavoc: FIXME: this code is *weird*, redesign it sometime
104         cycle = cl.time  / cl_bobcycle.value;
105         cycle -= (int) cycle;
106         if (cycle < cl_bobup.value)
107                 cycle = M_PI * cycle / cl_bobup.value;
108         else
109                 cycle = M_PI + M_PI*(cycle-cl_bobup.value)/(1.0 - cl_bobup.value);
110
111         // bob is proportional to velocity in the xy plane
112         // (don't count Z, or jumping messes it up)
113
114         bob = sqrt(cl.velocity[0]*cl.velocity[0] + cl.velocity[1]*cl.velocity[1]) * cl_bob.value;
115         //Con_Printf ("speed: %5.1f\n", Length(cl.velocity));
116         bob = bob*0.3 + bob*0.7*sin(cycle);
117         bob = bound(-7, bob, 4);
118         return bob;
119
120 }
121
122 void V_StartPitchDrift (void)
123 {
124 #if 1
125         if (cl.laststop == cl.time)
126         {
127                 return;         // something else is keeping it from drifting
128         }
129 #endif
130         if (cl.nodrift || !cl.pitchvel)
131         {
132                 cl.pitchvel = v_centerspeed.value;
133                 cl.nodrift = false;
134                 cl.driftmove = 0;
135         }
136 }
137
138 void V_StopPitchDrift (void)
139 {
140         cl.laststop = cl.time;
141         cl.nodrift = true;
142         cl.pitchvel = 0;
143 }
144
145 /*
146 ===============
147 V_DriftPitch
148
149 Moves the client pitch angle towards cl.idealpitch sent by the server.
150
151 If the user is adjusting pitch manually, either with lookup/lookdown,
152 mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped.
153
154 Drifting is enabled when the center view key is hit, mlook is released and
155 lookspring is non 0, or when
156 ===============
157 */
158 static void V_DriftPitch (void)
159 {
160         float           delta, move;
161
162         if (noclip_anglehack || !cl.onground || cls.demoplayback )
163         {
164                 cl.driftmove = 0;
165                 cl.pitchvel = 0;
166                 return;
167         }
168
169 // don't count small mouse motion
170         if (cl.nodrift)
171         {
172                 if ( fabs(cl.cmd.forwardmove) < cl_forwardspeed.value)
173                         cl.driftmove = 0;
174                 else
175                         cl.driftmove += cl.frametime;
176
177                 if ( cl.driftmove > v_centermove.value)
178                 {
179                         V_StartPitchDrift ();
180                 }
181                 return;
182         }
183
184         delta = cl.idealpitch - cl.viewangles[PITCH];
185
186         if (!delta)
187         {
188                 cl.pitchvel = 0;
189                 return;
190         }
191
192         move = cl.frametime * cl.pitchvel;
193         cl.pitchvel += cl.frametime * v_centerspeed.value;
194
195 //Con_Printf ("move: %f (%f)\n", move, cl.frametime);
196
197         if (delta > 0)
198         {
199                 if (move > delta)
200                 {
201                         cl.pitchvel = 0;
202                         move = delta;
203                 }
204                 cl.viewangles[PITCH] += move;
205         }
206         else if (delta < 0)
207         {
208                 if (move > -delta)
209                 {
210                         cl.pitchvel = 0;
211                         move = -delta;
212                 }
213                 cl.viewangles[PITCH] -= move;
214         }
215 }
216
217
218
219
220
221 /*
222 ==============================================================================
223
224                                                 SCREEN FLASHES
225
226 ==============================================================================
227 */
228
229
230 /*
231 ===============
232 V_ParseDamage
233 ===============
234 */
235 void V_ParseDamage (void)
236 {
237         int i, armor, blood;
238         vec3_t from, forward, right;
239         entity_t *ent;
240         float side, count;
241
242         armor = MSG_ReadByte ();
243         blood = MSG_ReadByte ();
244         for (i=0 ; i<3 ; i++)
245                 from[i] = MSG_ReadCoord ();
246
247         count = blood*0.5 + armor*0.5;
248         if (count < 10)
249                 count = 10;
250
251         cl.faceanimtime = cl.time + 0.2;                // put sbar face into pain frame
252
253         cl.cshifts[CSHIFT_DAMAGE].percent += 3*count;
254         if (cl.cshifts[CSHIFT_DAMAGE].percent < 0)
255                 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
256         if (cl.cshifts[CSHIFT_DAMAGE].percent > 150)
257                 cl.cshifts[CSHIFT_DAMAGE].percent = 150;
258
259         if (armor > blood)
260         {
261                 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 200;
262                 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 100;
263                 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 100;
264         }
265         else if (armor)
266         {
267                 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 220;
268                 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 50;
269                 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 50;
270         }
271         else
272         {
273                 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 255;
274                 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 0;
275                 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 0;
276         }
277
278 //
279 // calculate view angle kicks
280 //
281         ent = &cl_entities[cl.viewentity];
282
283         VectorSubtract (from, ent->render.origin, from);
284         VectorNormalize (from);
285
286         AngleVectors (ent->render.angles, forward, right, NULL);
287
288         side = DotProduct (from, right);
289         v_dmg_roll = count*side*v_kickroll.value;
290
291         side = DotProduct (from, forward);
292         v_dmg_pitch = count*side*v_kickpitch.value;
293
294         v_dmg_time = v_kicktime.value;
295 }
296
297 static cshift_t v_cshift;
298
299 /*
300 ==================
301 V_cshift_f
302 ==================
303 */
304 static void V_cshift_f (void)
305 {
306         v_cshift.destcolor[0] = atoi(Cmd_Argv(1));
307         v_cshift.destcolor[1] = atoi(Cmd_Argv(2));
308         v_cshift.destcolor[2] = atoi(Cmd_Argv(3));
309         v_cshift.percent = atoi(Cmd_Argv(4));
310 }
311
312
313 /*
314 ==================
315 V_BonusFlash_f
316
317 When you run over an item, the server sends this command
318 ==================
319 */
320 static void V_BonusFlash_f (void)
321 {
322         cl.cshifts[CSHIFT_BONUS].destcolor[0] = 215;
323         cl.cshifts[CSHIFT_BONUS].destcolor[1] = 186;
324         cl.cshifts[CSHIFT_BONUS].destcolor[2] = 69;
325         cl.cshifts[CSHIFT_BONUS].percent = 50;
326 }
327
328 /*
329 =============
330 V_UpdateBlends
331 =============
332 */
333 void V_UpdateBlends (void)
334 {
335         float   r, g, b, a, a2;
336         int             j;
337
338         if (cl.worldmodel == NULL)
339         {
340                 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
341                 cl.cshifts[CSHIFT_BONUS].percent = 0;
342                 cl.cshifts[CSHIFT_CONTENTS].percent = 0;
343                 cl.cshifts[CSHIFT_POWERUP].percent = 0;
344                 r_refdef.viewblend[0] = 0;
345                 r_refdef.viewblend[1] = 0;
346                 r_refdef.viewblend[2] = 0;
347                 r_refdef.viewblend[3] = 0;
348                 return;
349         }
350
351         // drop the damage value
352         cl.cshifts[CSHIFT_DAMAGE].percent -= (cl.time - cl.oldtime)*150;
353         if (cl.cshifts[CSHIFT_DAMAGE].percent <= 0)
354                 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
355
356         // drop the bonus value
357         cl.cshifts[CSHIFT_BONUS].percent -= (cl.time - cl.oldtime)*100;
358         if (cl.cshifts[CSHIFT_BONUS].percent <= 0)
359                 cl.cshifts[CSHIFT_BONUS].percent = 0;
360
361         // set contents color
362         switch (Mod_PointInLeaf (r_refdef.vieworg, cl.worldmodel)->contents)
363         {
364         case CONTENTS_EMPTY:
365         case CONTENTS_SOLID:
366                 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = v_cshift.destcolor[0];
367                 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = v_cshift.destcolor[1];
368                 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = v_cshift.destcolor[2];
369                 cl.cshifts[CSHIFT_CONTENTS].percent = v_cshift.percent;
370                 break;
371         case CONTENTS_LAVA:
372                 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 255;
373                 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80;
374                 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 0;
375                 cl.cshifts[CSHIFT_CONTENTS].percent = 150;
376                 break;
377         case CONTENTS_SLIME:
378                 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 0;
379                 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 25;
380                 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 5;
381                 cl.cshifts[CSHIFT_CONTENTS].percent = 150;
382                 break;
383         default:
384                 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 130;
385                 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80;
386                 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 50;
387                 cl.cshifts[CSHIFT_CONTENTS].percent = 128;
388         }
389
390         if (cl.items & IT_QUAD)
391         {
392                 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
393                 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 0;
394                 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 255;
395                 cl.cshifts[CSHIFT_POWERUP].percent = 30;
396         }
397         else if (cl.items & IT_SUIT)
398         {
399                 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
400                 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
401                 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
402                 cl.cshifts[CSHIFT_POWERUP].percent = 20;
403         }
404         else if (cl.items & IT_INVISIBILITY)
405         {
406                 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 100;
407                 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 100;
408                 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 100;
409                 cl.cshifts[CSHIFT_POWERUP].percent = 100;
410         }
411         else if (cl.items & IT_INVULNERABILITY)
412         {
413                 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 255;
414                 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
415                 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
416                 cl.cshifts[CSHIFT_POWERUP].percent = 30;
417         }
418         else
419                 cl.cshifts[CSHIFT_POWERUP].percent = 0;
420
421         // LordHavoc: fixed V_CalcBlend
422         r = 0;
423         g = 0;
424         b = 0;
425         a = 0;
426
427         for (j=0 ; j<NUM_CSHIFTS ; j++)
428         {
429                 a2 = cl.cshifts[j].percent * (1.0f / 255.0f);
430
431                 if (a2 < 0)
432                         continue;
433                 if (a2 > 1)
434                         a2 = 1;
435                 r += (cl.cshifts[j].destcolor[0]-r) * a2;
436                 g += (cl.cshifts[j].destcolor[1]-g) * a2;
437                 b += (cl.cshifts[j].destcolor[2]-b) * a2;
438                 a = 1 - (1 - a) * (1 - a2); // correct alpha multiply...  took a while to find it on the web
439         }
440         // saturate color (to avoid blending in black)
441         if (a)
442         {
443                 a2 = 1 / a;
444                 r *= a2;
445                 g *= a2;
446                 b *= a2;
447         }
448
449         r_refdef.viewblend[0] = bound(0, r * (1.0/255.0), 1);
450         r_refdef.viewblend[1] = bound(0, g * (1.0/255.0), 1);
451         r_refdef.viewblend[2] = bound(0, b * (1.0/255.0), 1);
452         r_refdef.viewblend[3] = bound(0, a              , 1);
453 }
454
455 /*
456 ==============================================================================
457
458                                                 VIEW RENDERING
459
460 ==============================================================================
461 */
462
463 /*
464 ==============
465 V_AddIdle
466
467 Idle swaying
468 ==============
469 */
470 static void V_AddIdle (float idle)
471 {
472         r_refdef.viewangles[ROLL] += idle * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
473         r_refdef.viewangles[PITCH] += idle * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
474         r_refdef.viewangles[YAW] += idle * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
475 }
476
477
478 /*
479 ==================
480 V_CalcRefdef
481
482 ==================
483 */
484 void V_CalcRefdef (void)
485 {
486         entity_t        *ent, *view;
487         vec3_t          forward;
488         vec3_t          angles;
489         float           bob;
490         float           side;
491
492         if (cls.state != ca_connected || !cl.worldmodel)
493                 return;
494
495         // ent is the player model (visible when out of body)
496         ent = &cl_entities[cl.viewentity];
497         // view is the weapon model (only visible from inside body)
498         view = &cl.viewent;
499
500         V_DriftPitch ();
501
502         VectorCopy (cl.viewentorigin, r_refdef.vieworg);
503         if (!intimerefresh)
504                 VectorCopy (cl.viewangles, r_refdef.viewangles);
505
506         if (cl.intermission)
507         {
508                 view->render.model = NULL;
509                 VectorCopy (ent->render.angles, r_refdef.viewangles);
510                 V_AddIdle (1);
511         }
512         else if (chase_active.value)
513         {
514                 r_refdef.vieworg[2] += cl.viewheight;
515                 Chase_Update ();
516                 V_AddIdle (v_idlescale.value);
517         }
518         else
519         {
520                 side = V_CalcRoll (cl_entities[cl.viewentity].render.angles, cl.velocity);
521                 r_refdef.viewangles[ROLL] += side;
522
523                 if (v_dmg_time > 0)
524                 {
525                         r_refdef.viewangles[ROLL] += v_dmg_time/v_kicktime.value*v_dmg_roll;
526                         r_refdef.viewangles[PITCH] += v_dmg_time/v_kicktime.value*v_dmg_pitch;
527                         v_dmg_time -= cl.frametime;
528                 }
529
530                 if (cl.stats[STAT_HEALTH] <= 0)
531                         r_refdef.viewangles[ROLL] = 80; // dead view angle
532
533                 V_AddIdle (v_idlescale.value);
534
535                 // offsets
536                 angles[PITCH] = -ent->render.angles[PITCH];     // because entity pitches are actually backward
537                 angles[YAW] = ent->render.angles[YAW];
538                 angles[ROLL] = ent->render.angles[ROLL];
539
540                 AngleVectors (angles, forward, NULL, NULL);
541
542                 bob = V_CalcBob ();
543
544                 r_refdef.vieworg[2] += cl.viewheight + bob;
545
546                 // set up gun
547                 view->state_current.modelindex = cl.stats[STAT_WEAPON];
548                 view->state_current.frame = cl.stats[STAT_WEAPONFRAME];
549                 VectorCopy(r_refdef.vieworg, view->render.origin);
550                 //view->render.origin[0] = ent->render.origin[0] + bob * 0.4 * forward[0];
551                 //view->render.origin[1] = ent->render.origin[1] + bob * 0.4 * forward[1];
552                 //view->render.origin[2] = ent->render.origin[2] + bob * 0.4 * forward[2] + cl.viewheight + bob;
553                 view->render.angles[PITCH] = -r_refdef.viewangles[PITCH] - v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
554                 view->render.angles[YAW] = r_refdef.viewangles[YAW] - v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
555                 view->render.angles[ROLL] = r_refdef.viewangles[ROLL] - v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
556                 // FIXME: this setup code is somewhat evil (CL_LerpUpdate should be private?)
557                 CL_LerpUpdate(view);
558                 view->render.colormap = -1; // no special coloring
559                 view->render.alpha = ent->render.alpha; // LordHavoc: if the player is transparent, so is the gun
560                 view->render.effects = ent->render.effects;
561                 view->render.scale = 1.0 / 3.0;
562
563                 // LordHavoc: origin view kick added
564                 if (!intimerefresh)
565                 {
566                         VectorAdd(r_refdef.viewangles, cl.punchangle, r_refdef.viewangles);
567                         VectorAdd(r_refdef.vieworg, cl.punchvector, r_refdef.vieworg);
568                 }
569
570                 // copy to refdef
571                 r_refdef.viewent = view->render;
572         }
573 }
574
575 //============================================================================
576
577 /*
578 =============
579 V_Init
580 =============
581 */
582 void V_Init (void)
583 {
584         Cmd_AddCommand ("v_cshift", V_cshift_f);
585         Cmd_AddCommand ("bf", V_BonusFlash_f);
586         Cmd_AddCommand ("centerview", V_StartPitchDrift);
587
588         Cvar_RegisterVariable (&v_centermove);
589         Cvar_RegisterVariable (&v_centerspeed);
590
591         Cvar_RegisterVariable (&v_iyaw_cycle);
592         Cvar_RegisterVariable (&v_iroll_cycle);
593         Cvar_RegisterVariable (&v_ipitch_cycle);
594         Cvar_RegisterVariable (&v_iyaw_level);
595         Cvar_RegisterVariable (&v_iroll_level);
596         Cvar_RegisterVariable (&v_ipitch_level);
597
598         Cvar_RegisterVariable (&v_idlescale);
599         Cvar_RegisterVariable (&crosshair);
600
601         Cvar_RegisterVariable (&cl_rollspeed);
602         Cvar_RegisterVariable (&cl_rollangle);
603         Cvar_RegisterVariable (&cl_bob);
604         Cvar_RegisterVariable (&cl_bobcycle);
605         Cvar_RegisterVariable (&cl_bobup);
606
607         Cvar_RegisterVariable (&v_kicktime);
608         Cvar_RegisterVariable (&v_kickroll);
609         Cvar_RegisterVariable (&v_kickpitch);
610 }
611
612