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