]> git.xonotic.org Git - xonotic/darkplaces.git/blob - view.c
simplify and clean up the gun model effects code a bit
[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 #include "cl_collision.h"
24
25 void CL_VM_UpdateDmgGlobals (int dmg_take, int dmg_save, vec3_t dmg_origin);
26
27 /*
28
29 The view is allowed to move slightly from it's true position for bobbing,
30 but if it exceeds 8 pixels linear distance (spherical, not box), the list of
31 entities sent from the server may not include everything in the pvs, especially
32 when crossing a water boudnary.
33
34 */
35
36 cvar_t cl_rollspeed = {0, "cl_rollspeed", "200", "how much strafing is necessary to tilt the view"};
37 cvar_t cl_rollangle = {0, "cl_rollangle", "2.0", "how much to tilt the view when strafing"};
38
39 cvar_t cl_bob = {CVAR_SAVE, "cl_bob","0.02", "view bobbing amount"};
40 cvar_t cl_bobcycle = {CVAR_SAVE, "cl_bobcycle","0.6", "view bobbing speed"};
41 cvar_t cl_bobup = {CVAR_SAVE, "cl_bobup","0.5", "view bobbing adjustment that makes the up or down swing of the bob last longer"};
42
43 cvar_t cl_bobmodel = {CVAR_SAVE, "cl_bobmodel", "1", "enables gun bobbing"};
44 cvar_t cl_bobmodel_side = {CVAR_SAVE, "cl_bobmodel_side", "0.15", "gun bobbing sideways sway amount"};
45 cvar_t cl_bobmodel_up = {CVAR_SAVE, "cl_bobmodel_up", "0.06", "gun bobbing upward movement amount"};
46 cvar_t cl_bobmodel_speed = {CVAR_SAVE, "cl_bobmodel_speed", "7", "gun bobbing speed"};
47
48 cvar_t cl_leanmodel_side = {CVAR_SAVE, "cl_leanmodel_side", "1", "enables gun leaning sideways"};
49 cvar_t cl_leanmodel_side_speed = {CVAR_SAVE, "cl_leanmodel_side_speed", "20", "gun leaning sideways speed"};
50 cvar_t cl_leanmodel_side_limit = {CVAR_SAVE, "cl_leanmodel_side_limit", "35", "gun leaning sideways limit"};
51 cvar_t cl_leanmodel_up = {CVAR_SAVE, "cl_leanmodel_up", "1", "enables gun leaning upward"};
52 cvar_t cl_leanmodel_up_speed = {CVAR_SAVE, "cl_leanmodel_up_speed", "15", "gun leaning upward speed"};
53 cvar_t cl_leanmodel_up_limit = {CVAR_SAVE, "cl_leanmodel_up_limit", "25", "gun leaning upward limit"};
54
55 cvar_t cl_followmodel_side = {CVAR_SAVE, "cl_followmodel_side", "1", "enables gun following sideways"};
56 cvar_t cl_followmodel_side_speed = {CVAR_SAVE, "cl_followmodel_side_speed", "0.015", "gun following sideways speed"};
57 cvar_t cl_followmodel_side_limit = {CVAR_SAVE, "cl_followmodel_side_limit", "2", "gun following sideways limit"};
58 cvar_t cl_followmodel_side_highpass = {CVAR_SAVE, "cl_followmodel_side_highpass", "2", "gun following sideways highpass"};
59 cvar_t cl_followmodel_up = {CVAR_SAVE, "cl_followmodel_up", "1", "enables gun following upward"};
60 cvar_t cl_followmodel_up_speed = {CVAR_SAVE, "cl_followmodel_up_speed", "0.01", "gun following upward speed"};
61 cvar_t cl_followmodel_up_limit = {CVAR_SAVE, "cl_followmodel_up_limit", "1.5", "gun following upward limit"};
62 cvar_t cl_followmodel_up_highpass = {CVAR_SAVE, "cl_followmodel_up_highpass", "2", "gun following upward highpass"};
63
64 cvar_t cl_viewmodel_scale = {0, "cl_viewmodel_scale", "1", "changes size of gun model, lower values prevent poking into walls but cause strange artifacts on lighting and especially r_stereo/vid_stereobuffer options where the size of the gun becomes visible"};
65
66 cvar_t v_kicktime = {0, "v_kicktime", "0.5", "how long a view kick from damage lasts"};
67 cvar_t v_kickroll = {0, "v_kickroll", "0.6", "how much a view kick from damage rolls your view"};
68 cvar_t v_kickpitch = {0, "v_kickpitch", "0.6", "how much a view kick from damage pitches your view"};
69
70 cvar_t v_iyaw_cycle = {0, "v_iyaw_cycle", "2", "v_idlescale yaw speed"};
71 cvar_t v_iroll_cycle = {0, "v_iroll_cycle", "0.5", "v_idlescale roll speed"};
72 cvar_t v_ipitch_cycle = {0, "v_ipitch_cycle", "1", "v_idlescale pitch speed"};
73 cvar_t v_iyaw_level = {0, "v_iyaw_level", "0.3", "v_idlescale yaw amount"};
74 cvar_t v_iroll_level = {0, "v_iroll_level", "0.1", "v_idlescale roll amount"};
75 cvar_t v_ipitch_level = {0, "v_ipitch_level", "0.3", "v_idlescale pitch amount"};
76
77 cvar_t v_idlescale = {0, "v_idlescale", "0", "how much of the quake 'drunken view' effect to use"};
78
79 cvar_t crosshair = {CVAR_SAVE, "crosshair", "0", "selects crosshair to use (0 is none)"};
80
81 cvar_t v_centermove = {0, "v_centermove", "0.15", "how long before the view begins to center itself (if freelook/+mlook/+jlook/+klook are off)"};
82 cvar_t v_centerspeed = {0, "v_centerspeed","500", "how fast the view centers itself"};
83
84 cvar_t cl_stairsmoothspeed = {CVAR_SAVE, "cl_stairsmoothspeed", "160", "how fast your view moves upward/downward when running up/down stairs"};
85
86 cvar_t chase_back = {CVAR_SAVE, "chase_back", "48", "chase cam distance from the player"};
87 cvar_t chase_up = {CVAR_SAVE, "chase_up", "24", "chase cam distance from the player"};
88 cvar_t chase_active = {CVAR_SAVE, "chase_active", "0", "enables chase cam"};
89 cvar_t chase_overhead = {CVAR_SAVE, "chase_overhead", "0", "chase cam looks straight down if this is not zero"};
90 // GAME_GOODVSBAD2
91 cvar_t chase_stevie = {0, "chase_stevie", "0", "chase cam view from above (used only by GoodVsBad2)"};
92
93 cvar_t v_deathtilt = {0, "v_deathtilt", "1", "whether to use sideways view when dead"};
94 cvar_t v_deathtiltangle = {0, "v_deathtiltangle", "80", "what roll angle to use when tilting the view while dead"};
95
96 // Prophecy camera pitchangle by Alexander "motorsep" Zubov
97 cvar_t chase_pitchangle = {CVAR_SAVE, "chase_pitchangle", "55", "chase cam pitch angle"};
98
99 float   v_dmg_time, v_dmg_roll, v_dmg_pitch;
100
101
102 /*
103 ===============
104 V_CalcRoll
105
106 Used by view and sv_user
107 ===============
108 */
109 float V_CalcRoll (vec3_t angles, vec3_t velocity)
110 {
111         vec3_t  right;
112         float   sign;
113         float   side;
114         float   value;
115
116         AngleVectors (angles, NULL, right, NULL);
117         side = DotProduct (velocity, right);
118         sign = side < 0 ? -1 : 1;
119         side = fabs(side);
120
121         value = cl_rollangle.value;
122
123         if (side < cl_rollspeed.value)
124                 side = side * value / cl_rollspeed.value;
125         else
126                 side = value;
127
128         return side*sign;
129
130 }
131
132 void V_StartPitchDrift (void)
133 {
134         if (cl.laststop == cl.time)
135                 return;         // something else is keeping it from drifting
136
137         if (cl.nodrift || !cl.pitchvel)
138         {
139                 cl.pitchvel = v_centerspeed.value;
140                 cl.nodrift = false;
141                 cl.driftmove = 0;
142         }
143 }
144
145 void V_StopPitchDrift (void)
146 {
147         cl.laststop = cl.time;
148         cl.nodrift = true;
149         cl.pitchvel = 0;
150 }
151
152 /*
153 ===============
154 V_DriftPitch
155
156 Moves the client pitch angle towards cl.idealpitch sent by the server.
157
158 If the user is adjusting pitch manually, either with lookup/lookdown,
159 mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped.
160
161 Drifting is enabled when the center view key is hit, mlook is released and
162 lookspring is non 0, or when
163 ===============
164 */
165 void V_DriftPitch (void)
166 {
167         float           delta, move;
168
169         if (noclip_anglehack || !cl.onground || cls.demoplayback )
170         {
171                 cl.driftmove = 0;
172                 cl.pitchvel = 0;
173                 return;
174         }
175
176 // don't count small mouse motion
177         if (cl.nodrift)
178         {
179                 if ( fabs(cl.cmd.forwardmove) < cl_forwardspeed.value)
180                         cl.driftmove = 0;
181                 else
182                         cl.driftmove += cl.realframetime;
183
184                 if ( cl.driftmove > v_centermove.value)
185                 {
186                         V_StartPitchDrift ();
187                 }
188                 return;
189         }
190
191         delta = cl.idealpitch - cl.viewangles[PITCH];
192
193         if (!delta)
194         {
195                 cl.pitchvel = 0;
196                 return;
197         }
198
199         move = cl.realframetime * cl.pitchvel;
200         cl.pitchvel += cl.realframetime * v_centerspeed.value;
201
202         if (delta > 0)
203         {
204                 if (move > delta)
205                 {
206                         cl.pitchvel = 0;
207                         move = delta;
208                 }
209                 cl.viewangles[PITCH] += move;
210         }
211         else if (delta < 0)
212         {
213                 if (move > -delta)
214                 {
215                         cl.pitchvel = 0;
216                         move = -delta;
217                 }
218                 cl.viewangles[PITCH] -= move;
219         }
220 }
221
222
223 /*
224 ==============================================================================
225
226                                                 SCREEN FLASHES
227
228 ==============================================================================
229 */
230
231
232 /*
233 ===============
234 V_ParseDamage
235 ===============
236 */
237 void V_ParseDamage (void)
238 {
239         int armor, blood;
240         vec3_t from;
241         //vec3_t forward, right;
242         vec3_t localfrom;
243         entity_t *ent;
244         //float side;
245         float count;
246
247         armor = MSG_ReadByte ();
248         blood = MSG_ReadByte ();
249         MSG_ReadVector(from, cls.protocol);
250
251         // Send the Dmg Globals to CSQC
252         CL_VM_UpdateDmgGlobals(blood, armor, from);
253
254         count = blood*0.5 + armor*0.5;
255         if (count < 10)
256                 count = 10;
257
258         cl.faceanimtime = cl.time + 0.2;                // put sbar face into pain frame
259
260         cl.cshifts[CSHIFT_DAMAGE].percent += 3*count;
261         cl.cshifts[CSHIFT_DAMAGE].alphafade = 150;
262         if (cl.cshifts[CSHIFT_DAMAGE].percent < 0)
263                 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
264         if (cl.cshifts[CSHIFT_DAMAGE].percent > 150)
265                 cl.cshifts[CSHIFT_DAMAGE].percent = 150;
266
267         if (armor > blood)
268         {
269                 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 200;
270                 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 100;
271                 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 100;
272         }
273         else if (armor)
274         {
275                 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 220;
276                 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 50;
277                 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 50;
278         }
279         else
280         {
281                 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 255;
282                 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 0;
283                 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 0;
284         }
285
286         // calculate view angle kicks
287         if (cl.entities[cl.viewentity].state_current.active)
288         {
289                 ent = &cl.entities[cl.viewentity];
290                 Matrix4x4_Transform(&ent->render.inversematrix, from, localfrom);
291                 VectorNormalize(localfrom);
292                 v_dmg_pitch = count * localfrom[0] * v_kickpitch.value;
293                 v_dmg_roll = count * localfrom[1] * v_kickroll.value;
294                 v_dmg_time = v_kicktime.value;
295         }
296 }
297
298 static cshift_t v_cshift;
299
300 /*
301 ==================
302 V_cshift_f
303 ==================
304 */
305 static void V_cshift_f (void)
306 {
307         v_cshift.destcolor[0] = atof(Cmd_Argv(1));
308         v_cshift.destcolor[1] = atof(Cmd_Argv(2));
309         v_cshift.destcolor[2] = atof(Cmd_Argv(3));
310         v_cshift.percent = atof(Cmd_Argv(4));
311 }
312
313
314 /*
315 ==================
316 V_BonusFlash_f
317
318 When you run over an item, the server sends this command
319 ==================
320 */
321 static void V_BonusFlash_f (void)
322 {
323         if(Cmd_Argc() == 1)
324         {
325                 cl.cshifts[CSHIFT_BONUS].destcolor[0] = 215;
326                 cl.cshifts[CSHIFT_BONUS].destcolor[1] = 186;
327                 cl.cshifts[CSHIFT_BONUS].destcolor[2] = 69;
328                 cl.cshifts[CSHIFT_BONUS].percent = 50;
329                 cl.cshifts[CSHIFT_BONUS].alphafade = 100;
330         }
331         else if(Cmd_Argc() >= 4 && Cmd_Argc() <= 6)
332         {
333                 cl.cshifts[CSHIFT_BONUS].destcolor[0] = atof(Cmd_Argv(1)) * 255;
334                 cl.cshifts[CSHIFT_BONUS].destcolor[1] = atof(Cmd_Argv(2)) * 255;
335                 cl.cshifts[CSHIFT_BONUS].destcolor[2] = atof(Cmd_Argv(3)) * 255;
336                 if(Cmd_Argc() >= 5)
337                         cl.cshifts[CSHIFT_BONUS].percent = atof(Cmd_Argv(4)) * 255; // yes, these are HEXADECIMAL percent ;)
338                 else
339                         cl.cshifts[CSHIFT_BONUS].percent = 50;
340                 if(Cmd_Argc() >= 6)
341                         cl.cshifts[CSHIFT_BONUS].alphafade = atof(Cmd_Argv(5)) * 255;
342                 else
343                         cl.cshifts[CSHIFT_BONUS].alphafade = 100;
344         }
345         else
346                 Con_Printf("usage:\nbf, or bf R G B [A [alphafade]]\n");
347 }
348
349 /*
350 ==============================================================================
351
352                                                 VIEW RENDERING
353
354 ==============================================================================
355 */
356
357 extern matrix4x4_t viewmodelmatrix;
358
359 #include "cl_collision.h"
360 #include "csprogs.h"
361
362 /*
363 ==================
364 V_CalcRefdef
365
366 ==================
367 */
368 #if 0
369 static vec3_t eyeboxmins = {-16, -16, -24};
370 static vec3_t eyeboxmaxs = { 16,  16,  32};
371 #endif
372
373 static vec_t lowpass(vec_t value, vec_t frac, vec_t *store)
374 {
375         frac = bound(0, frac, 1);
376         return (*store = *store * (1 - frac) + value * frac);
377 }
378
379 static vec_t highpass(vec_t value, vec_t frac, vec_t *store)
380 {
381         return value - lowpass(value, frac, store);
382 }
383
384 void V_CalcRefdef (void)
385 {
386         entity_t *ent;
387         float vieworg[3], viewangles[3], smoothtime;
388         float gunorg[3], gunangles[3];
389 #if 0
390 // begin of chase camera bounding box size for proper collisions by Alexander Zubov
391         vec3_t camboxmins = {-3, -3, -3};
392         vec3_t camboxmaxs = {3, 3, 3};
393 // end of chase camera bounding box size for proper collisions by Alexander Zubov
394 #endif
395         trace_t trace;
396         VectorClear(gunorg);
397         viewmodelmatrix = identitymatrix;
398         r_refdef.view.matrix = identitymatrix;
399         if (cls.state == ca_connected && cls.signon == SIGNONS)
400         {
401                 // ent is the view entity (visible when out of body)
402                 ent = &cl.entities[cl.viewentity];
403                 // player can look around, so take the origin from the entity,
404                 // and the angles from the input system
405                 Matrix4x4_OriginFromMatrix(&ent->render.matrix, vieworg);
406                 VectorCopy(cl.viewangles, viewangles);
407
408                 // calculate how much time has passed since the last V_CalcRefdef
409                 smoothtime = bound(0, cl.time - cl.stairsmoothtime, 0.1);
410                 cl.stairsmoothtime = cl.time;
411
412                 // fade damage flash
413                 if (v_dmg_time > 0)
414                         v_dmg_time -= bound(0, smoothtime, 0.1);
415
416                 if (cl.intermission)
417                 {
418                         // entity is a fixed camera, just copy the matrix
419                         if (cls.protocol == PROTOCOL_QUAKEWORLD)
420                                 Matrix4x4_CreateFromQuakeEntity(&r_refdef.view.matrix, cl.qw_intermission_origin[0], cl.qw_intermission_origin[1], cl.qw_intermission_origin[2], cl.qw_intermission_angles[0], cl.qw_intermission_angles[1], cl.qw_intermission_angles[2], 1);
421                         else
422                         {
423                                 r_refdef.view.matrix = ent->render.matrix;
424                                 Matrix4x4_AdjustOrigin(&r_refdef.view.matrix, 0, 0, cl.stats[STAT_VIEWHEIGHT]);
425                         }
426                         viewmodelmatrix = r_refdef.view.matrix;
427                 }
428                 else
429                 {
430                         // smooth stair stepping, but only if onground and enabled
431                         if (!cl.onground || cl_stairsmoothspeed.value <= 0)
432                                 cl.stairsmoothz = vieworg[2];
433                         else
434                         {
435                                 if (cl.stairsmoothz < vieworg[2])
436                                         vieworg[2] = cl.stairsmoothz = bound(vieworg[2] - 16, cl.stairsmoothz + smoothtime * cl_stairsmoothspeed.value, vieworg[2]);
437                                 else if (cl.stairsmoothz > vieworg[2])
438                                         vieworg[2] = cl.stairsmoothz = bound(vieworg[2], cl.stairsmoothz - smoothtime * cl_stairsmoothspeed.value, vieworg[2] + 16);
439                         }
440
441                         // apply qw weapon recoil effect (this did not work in QW)
442                         // TODO: add a cvar to disable this
443                         viewangles[PITCH] += cl.qw_weaponkick;
444
445                         // apply the viewofs (even if chasecam is used)
446                         vieworg[2] += cl.stats[STAT_VIEWHEIGHT];
447
448                         if (chase_active.value)
449                         {
450                                 // observing entity from third person. Added "campitch" by Alexander "motorsep" Zubov
451                                 vec_t camback, camup, dist, campitch, forward[3], chase_dest[3];
452
453                                 camback = chase_back.value;
454                                 camup = chase_up.value;
455                                 campitch = chase_pitchangle.value;
456
457                                 AngleVectors(viewangles, forward, NULL, NULL);
458
459                                 if (chase_overhead.integer)
460                                 {
461 #if 1
462                                         vec3_t offset;
463                                         vec3_t bestvieworg;
464 #endif
465                                         vec3_t up;
466                                         viewangles[PITCH] = 0;
467                                         AngleVectors(viewangles, forward, NULL, up);
468                                         // trace a little further so it hits a surface more consistently (to avoid 'snapping' on the edge of the range)
469                                         chase_dest[0] = vieworg[0] - forward[0] * camback + up[0] * camup;
470                                         chase_dest[1] = vieworg[1] - forward[1] * camback + up[1] * camup;
471                                         chase_dest[2] = vieworg[2] - forward[2] * camback + up[2] * camup;
472 #if 0
473 #if 1
474                                         //trace = CL_TraceLine(vieworg, eyeboxmins, eyeboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false);
475                                         trace = CL_TraceLine(vieworg, camboxmins, camboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false);
476 #else
477                                         //trace = CL_TraceBox(vieworg, eyeboxmins, eyeboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false);
478                                         trace = CL_TraceBox(vieworg, camboxmins, camboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false);
479 #endif
480                                         VectorCopy(trace.endpos, vieworg);
481                                         vieworg[2] -= 8;
482 #else
483                                         // trace from first person view location to our chosen third person view location
484 #if 1
485                                         trace = CL_TraceLine(vieworg, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false);
486 #else
487                                         trace = CL_TraceBox(vieworg, camboxmins, camboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false);
488 #endif
489                                         VectorCopy(trace.endpos, bestvieworg);
490                                         offset[2] = 0;
491                                         for (offset[0] = -16;offset[0] <= 16;offset[0] += 8)
492                                         {
493                                                 for (offset[1] = -16;offset[1] <= 16;offset[1] += 8)
494                                                 {
495                                                         AngleVectors(viewangles, NULL, NULL, up);
496                                                         chase_dest[0] = vieworg[0] - forward[0] * camback + up[0] * camup + offset[0];
497                                                         chase_dest[1] = vieworg[1] - forward[1] * camback + up[1] * camup + offset[1];
498                                                         chase_dest[2] = vieworg[2] - forward[2] * camback + up[2] * camup + offset[2];
499 #if 1
500                                                         trace = CL_TraceLine(vieworg, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false);
501 #else
502                                                         trace = CL_TraceBox(vieworg, camboxmins, camboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false);
503 #endif
504                                                         if (bestvieworg[2] > trace.endpos[2])
505                                                                 bestvieworg[2] = trace.endpos[2];
506                                                 }
507                                         }
508                                         bestvieworg[2] -= 8;
509                                         VectorCopy(bestvieworg, vieworg);
510 #endif
511                                         viewangles[PITCH] = campitch;
512                                 }
513                                 else
514                                 {
515                                         if (gamemode == GAME_GOODVSBAD2 && chase_stevie.integer)
516                                         {
517                                                 // look straight down from high above
518                                                 viewangles[PITCH] = 90;
519                                                 camback = 2048;
520                                                 VectorSet(forward, 0, 0, -1);
521                                         }
522
523                                         // trace a little further so it hits a surface more consistently (to avoid 'snapping' on the edge of the range)
524                                         dist = -camback - 8;
525                                         chase_dest[0] = vieworg[0] + forward[0] * dist;
526                                         chase_dest[1] = vieworg[1] + forward[1] * dist;
527                                         chase_dest[2] = vieworg[2] + forward[2] * dist + camup;
528                                         trace = CL_TraceLine(vieworg, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false);
529                                         VectorMAMAM(1, trace.endpos, 8, forward, 4, trace.plane.normal, vieworg);
530                                 }
531                         }
532                         else
533                         {
534                                 // first person view from entity
535                                 // angles
536                                 if (cl.stats[STAT_HEALTH] <= 0 && v_deathtilt.integer)
537                                         viewangles[ROLL] = v_deathtiltangle.value;
538                                 VectorAdd(viewangles, cl.punchangle, viewangles);
539                                 viewangles[ROLL] += V_CalcRoll(cl.viewangles, cl.movement_velocity);
540                                 if (v_dmg_time > 0)
541                                 {
542                                         viewangles[ROLL] += v_dmg_time/v_kicktime.value*v_dmg_roll;
543                                         viewangles[PITCH] += v_dmg_time/v_kicktime.value*v_dmg_pitch;
544                                 }
545                                 // origin
546                                 VectorAdd(vieworg, cl.punchvector, vieworg);
547                                 if (cl.stats[STAT_HEALTH] > 0)
548                                 {
549                                         double xyspeed, bob;
550                                         vec_t f;
551
552                                         // lowpass the view angles to the gun angles
553                                         if(cl.viewangles[PITCH] - cl.gunangles_lowpass[PITCH] >= 180)
554                                                 cl.gunangles_lowpass[PITCH] += 360;
555                                         if(cl.gunangles_lowpass[PITCH] - cl.viewangles[PITCH] >= 180)
556                                                 cl.gunangles_lowpass[PITCH] -= 360;
557                                         gunangles[PITCH] = bound(cl.viewangles[PITCH] - cl_leanmodel_up_limit.value, lowpass(cl.viewangles[PITCH], cl_leanmodel_up_speed.value * cl.realframetime * cl.movevars_timescale, &cl.gunangles_lowpass[PITCH]), cl.viewangles[PITCH] + cl_leanmodel_up_limit.value);
558
559                                         if(cl.viewangles[YAW] - cl.gunangles_lowpass[YAW] >= 180)
560                                                 cl.gunangles_lowpass[YAW] += 360;
561                                         if(cl.gunangles_lowpass[YAW] - cl.viewangles[YAW] >= 180)
562                                                 cl.gunangles_lowpass[YAW] -= 360;
563                                         gunangles[YAW] = bound(cl.viewangles[YAW] - cl_leanmodel_side_limit.value, lowpass(cl.viewangles[YAW], cl_leanmodel_side_speed.value * cl.realframetime * cl.movevars_timescale, &cl.gunangles_lowpass[YAW]), cl.viewangles[YAW] + cl_leanmodel_side_limit.value);
564
565                                         // no need to adjust these
566                                         gunangles[ROLL] = cl.viewangles[ROLL];
567
568                                         // the gun origin be the current origin minus highpass of the velocity
569                                         gunorg[0] = highpass(cl.movement_velocity[0] * cl_followmodel_side_speed.value, cl.realframetime * cl.movevars_timescale * cl_followmodel_side_highpass.value, &cl.gunorg_minus_vieworg_diff_highpass[0]);
570                                         gunorg[1] = highpass(cl.movement_velocity[1] * cl_followmodel_side_speed.value, cl.realframetime * cl.movevars_timescale * cl_followmodel_side_highpass.value, &cl.gunorg_minus_vieworg_diff_highpass[1]);
571                                         gunorg[2] = highpass(cl.movement_velocity[2] * cl_followmodel_up_speed.value, cl.realframetime * cl.movevars_timescale * cl_followmodel_up_highpass.value, &cl.gunorg_minus_vieworg_diff_highpass[2]);
572
573                                         // limit the gun origin
574                                         f = sqrt(gunorg[0]*gunorg[0] + gunorg[1]*gunorg[1]);
575                                         if(f > cl_followmodel_side_limit.value)
576                                         {
577                                                 gunorg[0] = gunorg[0] * cl_followmodel_side_limit.value / f;
578                                                 gunorg[1] = gunorg[1] * cl_followmodel_side_limit.value / f;
579                                         }
580                                         gunorg[2] = bound(-cl_followmodel_up_limit.value, gunorg[2], cl_followmodel_up_limit.value);
581
582                                         VectorSubtract(vieworg, gunorg, gunorg);
583
584                                         // view bobbing code
585                                         xyspeed = sqrt(cl.movement_velocity[0]*cl.movement_velocity[0] + cl.movement_velocity[1]*cl.movement_velocity[1]);
586                                         if (cl_bob.value && cl_bobcycle.value)
587                                         {
588                                                 float cycle;
589                                                 // LordHavoc: this code is *weird*, but not replacable (I think it
590                                                 // should be done in QC on the server, but oh well, quake is quake)
591                                                 // LordHavoc: figured out bobup: the time at which the sin is at 180
592                                                 // degrees (which allows lengthening or squishing the peak or valley)
593                                                 cycle = cl.time / cl_bobcycle.value;
594                                                 cycle -= (int) cycle;
595                                                 if (cycle < cl_bobup.value)
596                                                         cycle = sin(M_PI * cycle / cl_bobup.value);
597                                                 else
598                                                         cycle = sin(M_PI + M_PI * (cycle-cl_bobup.value)/(1.0 - cl_bobup.value));
599                                                 // bob is proportional to velocity in the xy plane
600                                                 // (don't count Z, or jumping messes it up)
601                                                 bob = xyspeed * cl_bob.value;
602                                                 bob = bob*0.3 + bob*0.7*cycle;
603                                                 vieworg[2] += bound(-7, bob, 4);
604                                                 // we also need to adjust gunorg, or this appears like pushing the gun!
605                                                 // In the old code, this was applied to vieworg BEFORE copying to gunorg,
606                                                 // but this is not viable with the new followmodel code as that would mean
607                                                 // that followmodel would work on the munged-by-bob vieworg and do feedback
608                                                 gunorg[2] += bound(-7, bob, 4);
609                                         }
610
611                                         // gun model bobbing code
612                                         if (cl_bob.value && cl_bobmodel.value)
613                                         {
614                                                 // calculate for swinging gun model
615                                                 // the gun bobs when running on the ground, but doesn't bob when you're in the air.
616                                                 // Sajt: I tried to smooth out the transitions between bob and no bob, which works
617                                                 // for the most part, but for some reason when you go through a message trigger or
618                                                 // pick up an item or anything like that it will momentarily jolt the gun.
619                                                 vec3_t forward, right, up;
620                                                 float bspeed;
621                                                 float s;
622                                                 float t;
623
624                                                 s = cl.time * cl_bobmodel_speed.value;
625                                                 if (cl.onground)
626                                                 {
627                                                         if (cl.time - cl.hitgroundtime < 0.2)
628                                                         {
629                                                                 // just hit the ground, speed the bob back up over the next 0.2 seconds
630                                                                 t = cl.time - cl.hitgroundtime;
631                                                                 t = bound(0, t, 0.2);
632                                                                 t *= 5;
633                                                         }
634                                                         else
635                                                                 t = 1;
636                                                 }
637                                                 else
638                                                 {
639                                                         // recently left the ground, slow the bob down over the next 0.2 seconds
640                                                         t = cl.time - cl.lastongroundtime;
641                                                         t = 0.2 - bound(0, t, 0.2);
642                                                         t *= 5;
643                                                 }
644
645                                                 bspeed = bound (0, xyspeed, 400) * 0.01f;
646                                                 AngleVectors (gunangles, forward, right, up);
647                                                 bob = bspeed * cl_bobmodel_side.value * cl_viewmodel_scale.value * sin (s) * t;
648                                                 VectorMA (gunorg, bob, right, gunorg);
649                                                 bob = bspeed * cl_bobmodel_up.value * cl_viewmodel_scale.value * cos (s * 2) * t;
650                                                 VectorMA (gunorg, bob, up, gunorg);
651                                         }
652                                 }
653                         }
654                         // calculate a view matrix for rendering the scene
655                         if (v_idlescale.value)
656                                 Matrix4x4_CreateFromQuakeEntity(&r_refdef.view.matrix, vieworg[0], vieworg[1], vieworg[2], viewangles[0] + v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value, viewangles[1] + v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value, viewangles[2] + v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value, 1);
657                         else
658                                 Matrix4x4_CreateFromQuakeEntity(&r_refdef.view.matrix, vieworg[0], vieworg[1], vieworg[2], viewangles[0], viewangles[1], viewangles[2] + v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value, 1);
659                         // calculate a viewmodel matrix for use in view-attached entities
660                         Matrix4x4_CreateFromQuakeEntity(&viewmodelmatrix, gunorg[0], gunorg[1], gunorg[2], gunangles[0], gunangles[1], gunangles[2], cl_viewmodel_scale.value);
661                         VectorCopy(vieworg, cl.csqc_origin);
662                         VectorCopy(viewangles, cl.csqc_angles);
663                 }
664         }
665 }
666
667 void V_FadeViewFlashs(void)
668 {
669         // don't flash if time steps backwards
670         if (cl.time <= cl.oldtime)
671                 return;
672         // drop the damage value
673         cl.cshifts[CSHIFT_DAMAGE].percent -= (cl.time - cl.oldtime)*cl.cshifts[CSHIFT_DAMAGE].alphafade;
674         if (cl.cshifts[CSHIFT_DAMAGE].percent <= 0)
675                 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
676         // drop the bonus value
677         cl.cshifts[CSHIFT_BONUS].percent -= (cl.time - cl.oldtime)*cl.cshifts[CSHIFT_BONUS].alphafade;
678         if (cl.cshifts[CSHIFT_BONUS].percent <= 0)
679                 cl.cshifts[CSHIFT_BONUS].percent = 0;
680 }
681
682 void V_CalcViewBlend(void)
683 {
684         float a2;
685         int j;
686         r_refdef.viewblend[0] = 0;
687         r_refdef.viewblend[1] = 0;
688         r_refdef.viewblend[2] = 0;
689         r_refdef.viewblend[3] = 0;
690         r_refdef.frustumscale_x = 1;
691         r_refdef.frustumscale_y = 1;
692         if (cls.state == ca_connected && cls.signon == SIGNONS)
693         {
694                 // set contents color
695                 int supercontents;
696                 vec3_t vieworigin;
697                 Matrix4x4_OriginFromMatrix(&r_refdef.view.matrix, vieworigin);
698                 supercontents = CL_PointSuperContents(vieworigin);
699                 if (supercontents & SUPERCONTENTS_LIQUIDSMASK)
700                 {
701                         r_refdef.frustumscale_x *= 1 - (((sin(cl.time * 4.7) + 1) * 0.015) * r_waterwarp.value);
702                         r_refdef.frustumscale_y *= 1 - (((sin(cl.time * 3.0) + 1) * 0.015) * r_waterwarp.value);
703                         if (supercontents & SUPERCONTENTS_LAVA)
704                         {
705                                 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 255;
706                                 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80;
707                                 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 0;
708                         }
709                         else if (supercontents & SUPERCONTENTS_SLIME)
710                         {
711                                 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 0;
712                                 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 25;
713                                 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 5;
714                         }
715                         else
716                         {
717                                 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 130;
718                                 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80;
719                                 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 50;
720                         }
721                         cl.cshifts[CSHIFT_CONTENTS].percent = 150 * 0.5;
722                 }
723                 else
724                 {
725                         cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 0;
726                         cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 0;
727                         cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 0;
728                         cl.cshifts[CSHIFT_CONTENTS].percent = 0;
729                 }
730
731                 if (gamemode != GAME_TRANSFUSION)
732                 {
733                         if (cl.stats[STAT_ITEMS] & IT_QUAD)
734                         {
735                                 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
736                                 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 0;
737                                 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 255;
738                                 cl.cshifts[CSHIFT_POWERUP].percent = 30;
739                         }
740                         else if (cl.stats[STAT_ITEMS] & IT_SUIT)
741                         {
742                                 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
743                                 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
744                                 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
745                                 cl.cshifts[CSHIFT_POWERUP].percent = 20;
746                         }
747                         else if (cl.stats[STAT_ITEMS] & IT_INVISIBILITY)
748                         {
749                                 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 100;
750                                 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 100;
751                                 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 100;
752                                 cl.cshifts[CSHIFT_POWERUP].percent = 100;
753                         }
754                         else if (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY)
755                         {
756                                 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 255;
757                                 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
758                                 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
759                                 cl.cshifts[CSHIFT_POWERUP].percent = 30;
760                         }
761                         else
762                                 cl.cshifts[CSHIFT_POWERUP].percent = 0;
763                 }
764
765                 cl.cshifts[CSHIFT_VCSHIFT].destcolor[0] = v_cshift.destcolor[0];
766                 cl.cshifts[CSHIFT_VCSHIFT].destcolor[1] = v_cshift.destcolor[1];
767                 cl.cshifts[CSHIFT_VCSHIFT].destcolor[2] = v_cshift.destcolor[2];
768                 cl.cshifts[CSHIFT_VCSHIFT].percent = v_cshift.percent;
769
770                 // LordHavoc: fixed V_CalcBlend
771                 for (j = 0;j < NUM_CSHIFTS;j++)
772                 {
773                         a2 = bound(0.0f, cl.cshifts[j].percent * (1.0f / 255.0f), 1.0f);
774                         if (a2 > 0)
775                         {
776                                 VectorLerp(r_refdef.viewblend, a2, cl.cshifts[j].destcolor, r_refdef.viewblend);
777                                 r_refdef.viewblend[3] = (1 - (1 - r_refdef.viewblend[3]) * (1 - a2)); // correct alpha multiply...  took a while to find it on the web
778                         }
779                 }
780                 // saturate color (to avoid blending in black)
781                 if (r_refdef.viewblend[3])
782                 {
783                         a2 = 1 / r_refdef.viewblend[3];
784                         VectorScale(r_refdef.viewblend, a2, r_refdef.viewblend);
785                 }
786                 r_refdef.viewblend[0] = bound(0.0f, r_refdef.viewblend[0] * (1.0f/255.0f), 1.0f);
787                 r_refdef.viewblend[1] = bound(0.0f, r_refdef.viewblend[1] * (1.0f/255.0f), 1.0f);
788                 r_refdef.viewblend[2] = bound(0.0f, r_refdef.viewblend[2] * (1.0f/255.0f), 1.0f);
789                 r_refdef.viewblend[3] = bound(0.0f, r_refdef.viewblend[3] * gl_polyblend.value, 1.0f);
790                 
791                 // Samual: Ugly hack, I know. But it's the best we can do since
792                 // there is no way to detect client states from the engine.
793                 if (cl.stats[STAT_HEALTH] <= 0 && cl.stats[STAT_HEALTH] != -666 && 
794                         cl.stats[STAT_HEALTH] != -2342 && cl_deathfade.value > 0)
795                 {
796                         cl.deathfade += cl_deathfade.value * max(0.00001, cl.time - cl.oldtime);
797                         cl.deathfade = bound(0.0f, cl.deathfade, 0.9f);
798                 }
799                 else
800                         cl.deathfade = 0.0f;
801
802                 if(cl.deathfade > 0)
803                 {
804                         float a;
805                         float deathfadevec[3] = {0.3f, 0.0f, 0.0f};
806                         a = r_refdef.viewblend[3] + cl.deathfade - r_refdef.viewblend[3]*cl.deathfade;
807                         if(a > 0)
808                                 VectorMAM(r_refdef.viewblend[3] * (1 - cl.deathfade) / a, r_refdef.viewblend, cl.deathfade / a, deathfadevec, r_refdef.viewblend);
809                         r_refdef.viewblend[3] = a;
810                 }
811         }
812 }
813
814 //============================================================================
815
816 /*
817 =============
818 V_Init
819 =============
820 */
821 void V_Init (void)
822 {
823         Cmd_AddCommand ("v_cshift", V_cshift_f, "sets tint color of view");
824         Cmd_AddCommand ("bf", V_BonusFlash_f, "briefly flashes a bright color tint on view (used when items are picked up); optionally takes R G B [A [alphafade]] arguments to specify how the flash looks");
825         Cmd_AddCommand ("centerview", V_StartPitchDrift, "gradually recenter view (stop looking up/down)");
826
827         Cvar_RegisterVariable (&v_centermove);
828         Cvar_RegisterVariable (&v_centerspeed);
829
830         Cvar_RegisterVariable (&v_iyaw_cycle);
831         Cvar_RegisterVariable (&v_iroll_cycle);
832         Cvar_RegisterVariable (&v_ipitch_cycle);
833         Cvar_RegisterVariable (&v_iyaw_level);
834         Cvar_RegisterVariable (&v_iroll_level);
835         Cvar_RegisterVariable (&v_ipitch_level);
836
837         Cvar_RegisterVariable (&v_idlescale);
838         Cvar_RegisterVariable (&crosshair);
839
840         Cvar_RegisterVariable (&cl_rollspeed);
841         Cvar_RegisterVariable (&cl_rollangle);
842         Cvar_RegisterVariable (&cl_bob);
843         Cvar_RegisterVariable (&cl_bobcycle);
844         Cvar_RegisterVariable (&cl_bobup);
845         Cvar_RegisterVariable (&cl_bobmodel);
846         Cvar_RegisterVariable (&cl_bobmodel_side);
847         Cvar_RegisterVariable (&cl_bobmodel_up);
848         Cvar_RegisterVariable (&cl_bobmodel_speed);
849
850         Cvar_RegisterVariable (&cl_leanmodel_side);
851         Cvar_RegisterVariable (&cl_leanmodel_side_speed);
852         Cvar_RegisterVariable (&cl_leanmodel_side_limit);
853         Cvar_RegisterVariable (&cl_leanmodel_up);
854         Cvar_RegisterVariable (&cl_leanmodel_up_speed);
855         Cvar_RegisterVariable (&cl_leanmodel_up_limit);
856
857         Cvar_RegisterVariable (&cl_followmodel_side);
858         Cvar_RegisterVariable (&cl_followmodel_side_speed);
859         Cvar_RegisterVariable (&cl_followmodel_side_limit);
860         Cvar_RegisterVariable (&cl_followmodel_side_highpass);
861         Cvar_RegisterVariable (&cl_followmodel_up);
862         Cvar_RegisterVariable (&cl_followmodel_up_speed);
863         Cvar_RegisterVariable (&cl_followmodel_up_limit);
864         Cvar_RegisterVariable (&cl_followmodel_up_highpass);
865
866         Cvar_RegisterVariable (&cl_viewmodel_scale);
867
868         Cvar_RegisterVariable (&v_kicktime);
869         Cvar_RegisterVariable (&v_kickroll);
870         Cvar_RegisterVariable (&v_kickpitch);
871
872         Cvar_RegisterVariable (&cl_stairsmoothspeed);
873
874         Cvar_RegisterVariable (&chase_back);
875         Cvar_RegisterVariable (&chase_up);
876         Cvar_RegisterVariable (&chase_active);
877         Cvar_RegisterVariable (&chase_overhead);
878         Cvar_RegisterVariable (&chase_pitchangle);
879         if (gamemode == GAME_GOODVSBAD2)
880                 Cvar_RegisterVariable (&chase_stevie);
881
882         Cvar_RegisterVariable (&v_deathtilt);
883         Cvar_RegisterVariable (&v_deathtiltangle);
884 }
885