]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - view.c
misc. cleanup, bubble trails fixed, improved lightmap compatibility.
[xonotic/darkplaces.git] / view.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20 // view.c -- player eye positioning
21
22 #include "quakedef.h"
23
24 /*
25
26 The view is allowed to move slightly from it's true position for bobbing,
27 but if it exceeds 8 pixels linear distance (spherical, not box), the list of
28 entities sent from the server may not include everything in the pvs, especially
29 when crossing a water boudnary.
30
31 */
32
33 cvar_t  scr_ofsx = {"scr_ofsx","0", false};
34 cvar_t  scr_ofsy = {"scr_ofsy","0", false};
35 cvar_t  scr_ofsz = {"scr_ofsz","0", false};
36
37 cvar_t  cl_rollspeed = {"cl_rollspeed", "200"};
38 cvar_t  cl_rollangle = {"cl_rollangle", "2.0"};
39
40 cvar_t  cl_bob = {"cl_bob","0.02", false};
41 cvar_t  cl_bobcycle = {"cl_bobcycle","0.6", false};
42 cvar_t  cl_bobup = {"cl_bobup","0.5", false};
43
44 cvar_t  v_kicktime = {"v_kicktime", "0.5", false};
45 cvar_t  v_kickroll = {"v_kickroll", "0.6", false};
46 cvar_t  v_kickpitch = {"v_kickpitch", "0.6", false};
47
48 cvar_t  v_iyaw_cycle = {"v_iyaw_cycle", "2", false};
49 cvar_t  v_iroll_cycle = {"v_iroll_cycle", "0.5", false};
50 cvar_t  v_ipitch_cycle = {"v_ipitch_cycle", "1", false};
51 cvar_t  v_iyaw_level = {"v_iyaw_level", "0.3", false};
52 cvar_t  v_iroll_level = {"v_iroll_level", "0.1", false};
53 cvar_t  v_ipitch_level = {"v_ipitch_level", "0.3", false};
54
55 cvar_t  v_idlescale = {"v_idlescale", "0", false};
56
57 cvar_t  crosshair = {"crosshair", "0", true};
58 cvar_t  cl_crossx = {"cl_crossx", "0", false};
59 cvar_t  cl_crossy = {"cl_crossy", "0", false};
60
61 cvar_t  gl_cshiftpercent = {"gl_cshiftpercent", "100", false};
62
63 float   v_dmg_time, v_dmg_roll, v_dmg_pitch;
64
65 extern  int                     in_forward, in_forward2, in_back;
66
67
68 /*
69 ===============
70 V_CalcRoll
71
72 Used by view and sv_user
73 ===============
74 */
75 vec3_t  forward, right, up;
76
77 float V_CalcRoll (vec3_t angles, vec3_t velocity)
78 {
79         float   sign;
80         float   side;
81         float   value;
82         
83         AngleVectors (angles, forward, right, up);
84         side = DotProduct (velocity, right);
85         sign = side < 0 ? -1 : 1;
86         side = fabs(side);
87         
88         value = cl_rollangle.value;
89 //      if (cl.inwater)
90 //              value *= 6;
91
92         if (side < cl_rollspeed.value)
93                 side = side * value / cl_rollspeed.value;
94         else
95                 side = value;
96         
97         return side*sign;
98         
99 }
100
101
102 /*
103 ===============
104 V_CalcBob
105
106 ===============
107 */
108 float V_CalcBob (void)
109 {
110         float   bob;
111         float   cycle;
112         
113         cycle = cl.time - (int)(cl.time/cl_bobcycle.value)*cl_bobcycle.value;
114         cycle /= cl_bobcycle.value;
115         if (cycle < cl_bobup.value)
116                 cycle = M_PI * cycle / cl_bobup.value;
117         else
118                 cycle = M_PI + M_PI*(cycle-cl_bobup.value)/(1.0 - cl_bobup.value);
119
120 // bob is proportional to velocity in the xy plane
121 // (don't count Z, or jumping messes it up)
122
123         bob = sqrt(cl.velocity[0]*cl.velocity[0] + cl.velocity[1]*cl.velocity[1]) * cl_bob.value;
124 //Con_Printf ("speed: %5.1f\n", Length(cl.velocity));
125         bob = bob*0.3 + bob*0.7*sin(cycle);
126         if (bob > 4)
127                 bob = 4;
128         else if (bob < -7)
129                 bob = -7;
130         return bob;
131         
132 }
133
134
135 //=============================================================================
136
137
138 cvar_t  v_centermove = {"v_centermove", "0.15", false};
139 cvar_t  v_centerspeed = {"v_centerspeed","500"};
140
141
142 void V_StartPitchDrift (void)
143 {
144 #if 1
145         if (cl.laststop == cl.time)
146         {
147                 return;         // something else is keeping it from drifting
148         }
149 #endif
150         if (cl.nodrift || !cl.pitchvel)
151         {
152                 cl.pitchvel = v_centerspeed.value;
153                 cl.nodrift = false;
154                 cl.driftmove = 0;
155         }
156 }
157
158 void V_StopPitchDrift (void)
159 {
160         cl.laststop = cl.time;
161         cl.nodrift = true;
162         cl.pitchvel = 0;
163 }
164
165 /*
166 ===============
167 V_DriftPitch
168
169 Moves the client pitch angle towards cl.idealpitch sent by the server.
170
171 If the user is adjusting pitch manually, either with lookup/lookdown,
172 mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped.
173
174 Drifting is enabled when the center view key is hit, mlook is released and
175 lookspring is non 0, or when 
176 ===============
177 */
178 void V_DriftPitch (void)
179 {
180         float           delta, move;
181
182         if (noclip_anglehack || !cl.onground || cls.demoplayback )
183         {
184                 cl.driftmove = 0;
185                 cl.pitchvel = 0;
186                 return;
187         }
188
189 // don't count small mouse motion
190         if (cl.nodrift)
191         {
192                 if ( fabs(cl.cmd.forwardmove) < cl_forwardspeed.value)
193                         cl.driftmove = 0;
194                 else
195                         cl.driftmove += host_frametime;
196         
197                 if ( cl.driftmove > v_centermove.value)
198                 {
199                         V_StartPitchDrift ();
200                 }
201                 return;
202         }
203         
204         delta = cl.idealpitch - cl.viewangles[PITCH];
205
206         if (!delta)
207         {
208                 cl.pitchvel = 0;
209                 return;
210         }
211
212         move = host_frametime * cl.pitchvel;
213         cl.pitchvel += host_frametime * v_centerspeed.value;
214         
215 //Con_Printf ("move: %f (%f)\n", move, host_frametime);
216
217         if (delta > 0)
218         {
219                 if (move > delta)
220                 {
221                         cl.pitchvel = 0;
222                         move = delta;
223                 }
224                 cl.viewangles[PITCH] += move;
225         }
226         else if (delta < 0)
227         {
228                 if (move > -delta)
229                 {
230                         cl.pitchvel = 0;
231                         move = -delta;
232                 }
233                 cl.viewangles[PITCH] -= move;
234         }
235 }
236
237
238
239
240
241 /*
242 ============================================================================== 
243  
244                                                 PALETTE FLASHES 
245  
246 ============================================================================== 
247 */ 
248  
249  
250 cshift_t        cshift_empty = { {130,80,50}, 0 };
251 cshift_t        cshift_water = { {130,80,50}, 128 };
252 cshift_t        cshift_slime = { {0,25,5}, 150 };
253 cshift_t        cshift_lava = { {255,80,0}, 150 };
254
255 byte            ramps[3][256];
256 float           v_blend[4];             // rgba 0.0 - 1.0
257
258 /*
259 ===============
260 V_ParseDamage
261 ===============
262 */
263 void V_ParseDamage (void)
264 {
265         int             armor, blood;
266         vec3_t  from;
267         int             i;
268         vec3_t  forward, right, up;
269         entity_t        *ent;
270         float   side;
271         float   count;
272         
273         armor = MSG_ReadByte ();
274         blood = MSG_ReadByte ();
275         for (i=0 ; i<3 ; i++)
276                 from[i] = MSG_ReadCoord ();
277
278         count = blood*0.5 + armor*0.5;
279         if (count < 10)
280                 count = 10;
281
282         cl.faceanimtime = cl.time + 0.2;                // but sbar face into pain frame
283
284         cl.cshifts[CSHIFT_DAMAGE].percent += 3*count;
285         if (cl.cshifts[CSHIFT_DAMAGE].percent < 0)
286                 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
287         if (cl.cshifts[CSHIFT_DAMAGE].percent > 150)
288                 cl.cshifts[CSHIFT_DAMAGE].percent = 150;
289
290         if (armor > blood)              
291         {
292                 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 200;
293                 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 100;
294                 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 100;
295         }
296         else if (armor)
297         {
298                 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 220;
299                 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 50;
300                 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 50;
301         }
302         else
303         {
304                 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 255;
305                 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 0;
306                 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 0;
307         }
308
309 //
310 // calculate view angle kicks
311 //
312         ent = &cl_entities[cl.viewentity];
313         
314         VectorSubtract (from, ent->origin, from);
315         VectorNormalize (from);
316         
317         AngleVectors (ent->angles, forward, right, up);
318
319         side = DotProduct (from, right);
320         v_dmg_roll = count*side*v_kickroll.value;
321         
322         side = DotProduct (from, forward);
323         v_dmg_pitch = count*side*v_kickpitch.value;
324
325         v_dmg_time = v_kicktime.value;
326 }
327
328
329 /*
330 ==================
331 V_cshift_f
332 ==================
333 */
334 void V_cshift_f (void)
335 {
336         cshift_empty.destcolor[0] = atoi(Cmd_Argv(1));
337         cshift_empty.destcolor[1] = atoi(Cmd_Argv(2));
338         cshift_empty.destcolor[2] = atoi(Cmd_Argv(3));
339         cshift_empty.percent = atoi(Cmd_Argv(4));
340 }
341
342
343 /*
344 ==================
345 V_BonusFlash_f
346
347 When you run over an item, the server sends this command
348 ==================
349 */
350 void V_BonusFlash_f (void)
351 {
352         cl.cshifts[CSHIFT_BONUS].destcolor[0] = 215;
353         cl.cshifts[CSHIFT_BONUS].destcolor[1] = 186;
354         cl.cshifts[CSHIFT_BONUS].destcolor[2] = 69;
355         cl.cshifts[CSHIFT_BONUS].percent = 50;
356 }
357
358 /*
359 =============
360 V_SetContentsColor
361
362 Underwater, lava, etc each has a color shift
363 =============
364 */
365 void V_SetContentsColor (int contents)
366 {
367         cshift_t* c;
368         c = &cl.cshifts[CSHIFT_CONTENTS]; // just to shorten the code below
369         switch (contents)
370         {
371         case CONTENTS_EMPTY:
372         case CONTENTS_SOLID:
373                 //cl.cshifts[CSHIFT_CONTENTS] = cshift_empty;
374                 c->destcolor[0] = cshift_empty.destcolor[0];
375                 c->destcolor[1] = cshift_empty.destcolor[1];
376                 c->destcolor[2] = cshift_empty.destcolor[2];
377                 c->percent = cshift_empty.percent;
378                 break;
379         case CONTENTS_LAVA:
380                 //cl.cshifts[CSHIFT_CONTENTS] = cshift_lava;
381                 c->destcolor[0] = cshift_lava.destcolor[0];
382                 c->destcolor[1] = cshift_lava.destcolor[1];
383                 c->destcolor[2] = cshift_lava.destcolor[2];
384                 c->percent = cshift_lava.percent;
385                 break;
386         case CONTENTS_SLIME:
387                 //cl.cshifts[CSHIFT_CONTENTS] = cshift_slime;
388                 c->destcolor[0] = cshift_slime.destcolor[0];
389                 c->destcolor[1] = cshift_slime.destcolor[1];
390                 c->destcolor[2] = cshift_slime.destcolor[2];
391                 c->percent = cshift_slime.percent;
392                 break;
393         default:
394                 //cl.cshifts[CSHIFT_CONTENTS] = cshift_water;
395                 c->destcolor[0] = cshift_water.destcolor[0];
396                 c->destcolor[1] = cshift_water.destcolor[1];
397                 c->destcolor[2] = cshift_water.destcolor[2];
398                 c->percent = cshift_water.percent;
399         }
400 }
401
402 /*
403 =============
404 V_CalcPowerupCshift
405 =============
406 */
407 void V_CalcPowerupCshift (void)
408 {
409         if (cl.items & IT_QUAD)
410         {
411                 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
412                 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 0;
413                 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 255;
414                 cl.cshifts[CSHIFT_POWERUP].percent = 30;
415         }
416         else if (cl.items & IT_SUIT)
417         {
418                 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
419                 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
420                 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
421                 cl.cshifts[CSHIFT_POWERUP].percent = 20;
422         }
423         else if (cl.items & IT_INVISIBILITY)
424         {
425                 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 100;
426                 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 100;
427                 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 100;
428                 cl.cshifts[CSHIFT_POWERUP].percent = 100;
429         }
430         else if (cl.items & IT_INVULNERABILITY)
431         {
432                 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 255;
433                 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
434                 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
435                 cl.cshifts[CSHIFT_POWERUP].percent = 30;
436         }
437         else
438                 cl.cshifts[CSHIFT_POWERUP].percent = 0;
439 }
440
441 /*
442 =============
443 V_CalcBlend
444 =============
445 */
446 // LordHavoc: fixed V_CalcBlend
447 void V_CalcBlend (void)
448 {
449         float   r, g, b, a, a2;
450         int             j;
451
452         r = 0;
453         g = 0;
454         b = 0;
455         a = 0;
456
457         if (gl_cshiftpercent.value)
458         {
459                 for (j=0 ; j<NUM_CSHIFTS ; j++) 
460                 {
461                         a2 = ((cl.cshifts[j].percent * gl_cshiftpercent.value) / 100.0) / 255.0;
462
463                         if (!a2)
464                                 continue;
465                         if (a2 > 1)
466                                 a2 = 1;
467                         r += (cl.cshifts[j].destcolor[0]-r) * a2;
468                         g += (cl.cshifts[j].destcolor[1]-g) * a2;
469                         b += (cl.cshifts[j].destcolor[2]-b) * a2;
470                         a = 1 - (1 - a) * (1 - a2); // correct alpha multiply...  took a while to find it on the web
471                 }
472                 // saturate color (to avoid blending in black)
473                 if (a)
474                 {
475                         a2 = 1 / a;
476                         r *= a2;
477                         g *= a2;
478                         b *= a2;
479                 }
480         }
481
482         v_blend[0] = bound(0, r * (1.0/255.0), 1);
483         v_blend[1] = bound(0, g * (1.0/255.0), 1);
484         v_blend[2] = bound(0, b * (1.0/255.0), 1);
485         v_blend[3] = bound(0, a              , 1);
486 }
487
488 /*
489 =============
490 V_UpdatePalette
491 =============
492 */
493 void V_UpdatePalette (void)
494 {
495         int             i, j;
496         qboolean        new;
497
498         V_CalcPowerupCshift ();
499         
500         new = false;
501         
502         for (i=0 ; i<NUM_CSHIFTS ; i++)
503         {
504                 if (cl.cshifts[i].percent != cl.prev_cshifts[i].percent)
505                 {
506                         new = true;
507                         cl.prev_cshifts[i].percent = cl.cshifts[i].percent;
508                 }
509                 for (j=0 ; j<3 ; j++)
510                         if (cl.cshifts[i].destcolor[j] != cl.prev_cshifts[i].destcolor[j])
511                         {
512                                 new = true;
513                                 cl.prev_cshifts[i].destcolor[j] = cl.cshifts[i].destcolor[j];
514                         }
515         }
516         
517 // drop the damage value
518         cl.cshifts[CSHIFT_DAMAGE].percent -= host_frametime*150;
519         if (cl.cshifts[CSHIFT_DAMAGE].percent <= 0)
520                 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
521
522 // drop the bonus value
523         cl.cshifts[CSHIFT_BONUS].percent -= host_frametime*100;
524         if (cl.cshifts[CSHIFT_BONUS].percent <= 0)
525                 cl.cshifts[CSHIFT_BONUS].percent = 0;
526
527         if (!new)
528                 return;
529
530         V_CalcBlend ();
531 }
532
533 /* 
534 ============================================================================== 
535  
536                                                 VIEW RENDERING 
537  
538 ============================================================================== 
539 */ 
540
541 float angledelta (float a)
542 {
543         a = anglemod(a);
544         if (a > 180)
545                 a -= 360;
546         return a;
547 }
548
549 /*
550 ==================
551 CalcGunAngle
552 ==================
553 */
554 void CalcGunAngle (void)
555 {       
556         float   yaw, pitch, move;
557         static float oldyaw = 0;
558         static float oldpitch = 0;
559         
560         yaw = r_refdef.viewangles[YAW];
561         pitch = -r_refdef.viewangles[PITCH];
562
563         yaw = angledelta(yaw - r_refdef.viewangles[YAW]) * 0.4;
564         if (yaw > 10)
565                 yaw = 10;
566         if (yaw < -10)
567                 yaw = -10;
568         pitch = angledelta(-pitch - r_refdef.viewangles[PITCH]) * 0.4;
569         if (pitch > 10)
570                 pitch = 10;
571         if (pitch < -10)
572                 pitch = -10;
573         move = host_frametime*20;
574         if (yaw > oldyaw)
575         {
576                 if (oldyaw + move < yaw)
577                         yaw = oldyaw + move;
578         }
579         else
580         {
581                 if (oldyaw - move > yaw)
582                         yaw = oldyaw - move;
583         }
584         
585         if (pitch > oldpitch)
586         {
587                 if (oldpitch + move < pitch)
588                         pitch = oldpitch + move;
589         }
590         else
591         {
592                 if (oldpitch - move > pitch)
593                         pitch = oldpitch - move;
594         }
595         
596         oldyaw = yaw;
597         oldpitch = pitch;
598
599         cl.viewent.angles[YAW] = r_refdef.viewangles[YAW] + yaw;
600         cl.viewent.angles[PITCH] = - (r_refdef.viewangles[PITCH] + pitch);
601
602         cl.viewent.angles[ROLL] -= v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
603         cl.viewent.angles[PITCH] -= v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
604         cl.viewent.angles[YAW] -= v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
605 }
606
607 /*
608 ==============
609 V_BoundOffsets
610 ==============
611 */
612 void V_BoundOffsets (void)
613 {
614         entity_t        *ent;
615         
616         ent = &cl_entities[cl.viewentity];
617
618 // absolutely bound refresh reletive to entity clipping hull
619 // so the view can never be inside a solid wall
620
621         if (r_refdef.vieworg[0] < ent->origin[0] - 14)
622                 r_refdef.vieworg[0] = ent->origin[0] - 14;
623         else if (r_refdef.vieworg[0] > ent->origin[0] + 14)
624                 r_refdef.vieworg[0] = ent->origin[0] + 14;
625         if (r_refdef.vieworg[1] < ent->origin[1] - 14)
626                 r_refdef.vieworg[1] = ent->origin[1] - 14;
627         else if (r_refdef.vieworg[1] > ent->origin[1] + 14)
628                 r_refdef.vieworg[1] = ent->origin[1] + 14;
629         if (r_refdef.vieworg[2] < ent->origin[2] - 22)
630                 r_refdef.vieworg[2] = ent->origin[2] - 22;
631         else if (r_refdef.vieworg[2] > ent->origin[2] + 30)
632                 r_refdef.vieworg[2] = ent->origin[2] + 30;
633 }
634
635 /*
636 ==============
637 V_AddIdle
638
639 Idle swaying
640 ==============
641 */
642 void V_AddIdle (void)
643 {
644         r_refdef.viewangles[ROLL] += v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
645         r_refdef.viewangles[PITCH] += v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
646         r_refdef.viewangles[YAW] += v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
647 }
648
649
650 /*
651 ==============
652 V_CalcViewRoll
653
654 Roll is induced by movement and damage
655 ==============
656 */
657 void V_CalcViewRoll (void)
658 {
659         float           side;
660                 
661         side = V_CalcRoll (cl_entities[cl.viewentity].angles, cl.velocity);
662         r_refdef.viewangles[ROLL] += side;
663
664         if (v_dmg_time > 0)
665         {
666                 r_refdef.viewangles[ROLL] += v_dmg_time/v_kicktime.value*v_dmg_roll;
667                 r_refdef.viewangles[PITCH] += v_dmg_time/v_kicktime.value*v_dmg_pitch;
668                 v_dmg_time -= host_frametime;
669         }
670
671         if (cl.stats[STAT_HEALTH] <= 0)
672         {
673                 r_refdef.viewangles[ROLL] = 80; // dead view angle
674                 return;
675         }
676
677 }
678
679
680 /*
681 ==================
682 V_CalcIntermissionRefdef
683
684 ==================
685 */
686 void V_CalcIntermissionRefdef (void)
687 {
688         entity_t        *ent, *view;
689         float           old;
690
691 // ent is the player model (visible when out of body)
692         ent = &cl_entities[cl.viewentity];
693 // view is the weapon model (only visible from inside body)
694         view = &cl.viewent;
695
696         VectorCopy (ent->origin, r_refdef.vieworg);
697         VectorCopy (ent->angles, r_refdef.viewangles);
698         view->model = NULL;
699
700 // allways idle in intermission
701         old = v_idlescale.value;
702         v_idlescale.value = 1;
703         V_AddIdle ();
704         v_idlescale.value = old;
705 }
706
707 /*
708 ==================
709 V_CalcRefdef
710
711 ==================
712 */
713 void V_CalcRefdef (void)
714 {
715         entity_t        *ent, *view;
716         int                     i;
717         vec3_t          forward, right, up;
718         vec3_t          angles;
719         float           bob;
720         static float oldz = 0;
721
722         V_DriftPitch ();
723
724 // ent is the player model (visible when out of body)
725         ent = &cl_entities[cl.viewentity];
726 // view is the weapon model (only visible from inside body)
727         view = &cl.viewent;
728         
729
730 // transform the view offset by the model's matrix to get the offset from
731 // model origin for the view
732         ent->angles[YAW] = cl.viewangles[YAW];  // the model should face the view dir
733         ent->angles[PITCH] = -cl.viewangles[PITCH];     // the model should face the view dir
734                                                                                 
735         
736         bob = V_CalcBob ();
737         
738 // refresh position
739         VectorCopy (ent->origin, r_refdef.vieworg);
740         r_refdef.vieworg[2] += cl.viewheight + bob;
741
742 // never let it sit exactly on a node line, because a water plane can
743 // dissapear when viewed with the eye exactly on it.
744 // the server protocol only specifies to 1/16 pixel, so add 1/32 in each axis
745         r_refdef.vieworg[0] += 1.0/32;
746         r_refdef.vieworg[1] += 1.0/32;
747         r_refdef.vieworg[2] += 1.0/32;
748
749         VectorCopy (cl.viewangles, r_refdef.viewangles);
750         V_CalcViewRoll ();
751         V_AddIdle ();
752
753 // offsets
754         angles[PITCH] = -ent->angles[PITCH];    // because entity pitches are
755                                                                                         //  actually backward
756         angles[YAW] = ent->angles[YAW];
757         angles[ROLL] = ent->angles[ROLL];
758
759         AngleVectors (angles, forward, right, up);
760
761         for (i=0 ; i<3 ; i++)
762                 r_refdef.vieworg[i] += scr_ofsx.value*forward[i]
763                         + scr_ofsy.value*right[i]
764                         + scr_ofsz.value*up[i];
765         
766         
767         V_BoundOffsets ();
768                 
769 // set up gun position
770         VectorCopy (cl.viewangles, view->angles);
771         
772         CalcGunAngle ();
773
774         VectorCopy (ent->origin, view->origin);
775         view->origin[2] += cl.viewheight;
776
777         for (i=0 ; i<3 ; i++)
778         {
779                 view->origin[i] += forward[i]*bob*0.4;
780 //              view->origin[i] += right[i]*bob*0.4;
781 //              view->origin[i] += up[i]*bob*0.8;
782         }
783         view->origin[2] += bob;
784
785 // fudge position around to keep amount of weapon visible
786 // roughly equal with different FOV
787
788 #if 0
789         if (cl.model_precache[cl.stats[STAT_WEAPON]] && strcmp (cl.model_precache[cl.stats[STAT_WEAPON]]->name,  "progs/v_shot2.mdl"))
790 #endif
791 // LordHavoc: everyone hates the gun moving around
792 /*
793         if (scr_viewsize.value == 110)
794                 view->origin[2] += 1;
795         else if (scr_viewsize.value == 100)
796                 view->origin[2] += 2;
797         else if (scr_viewsize.value == 90)
798                 view->origin[2] += 1;
799         else if (scr_viewsize.value == 80)
800                 view->origin[2] += 0.5;
801 */
802
803         view->model = cl.model_precache[cl.stats[STAT_WEAPON]];
804         view->frame = cl.stats[STAT_WEAPONFRAME];
805         view->colormap = vid.colormap;
806
807 // set up the refresh position
808         VectorAdd (r_refdef.viewangles, cl.punchangle, r_refdef.viewangles);
809
810 // smooth out stair step ups
811 if (cl.onground && ent->origin[2] - oldz > 0)
812 {
813         float steptime;
814         
815         steptime = cl.time - cl.oldtime;
816         if (steptime < 0)
817 //FIXME         I_Error ("steptime < 0");
818                 steptime = 0;
819
820         oldz += steptime * 80;
821         if (oldz > ent->origin[2])
822                 oldz = ent->origin[2];
823         if (ent->origin[2] - oldz > 12)
824                 oldz = ent->origin[2] - 12;
825         r_refdef.vieworg[2] += oldz - ent->origin[2];
826         view->origin[2] += oldz - ent->origin[2];
827 }
828 else
829         oldz = ent->origin[2];
830
831         if (chase_active.value)
832                 Chase_Update ();
833 }
834
835 /*
836 ==================
837 V_RenderView
838
839 The player's clipping box goes from (-16 -16 -24) to (16 16 32) from
840 the entity origin, so any view position inside that will be valid
841 ==================
842 */
843 extern vrect_t  scr_vrect;
844
845 void V_RenderView (void)
846 {
847         if (con_forcedup)
848                 return;
849
850 // don't allow cheats in multiplayer
851         if (cl.maxclients > 1)
852         {
853                 Cvar_Set ("scr_ofsx", "0");
854                 Cvar_Set ("scr_ofsy", "0");
855                 Cvar_Set ("scr_ofsz", "0");
856         }
857
858         if (cl.intermission)
859         {       // intermission / finale rendering
860                 V_CalcIntermissionRefdef ();    
861         }
862         else
863         {
864                 if (!cl.paused /* && (sv.maxclients > 1 || key_dest == key_game) */ )
865                         V_CalcRefdef ();
866         }
867
868         R_PushDlights ();
869
870         R_RenderView ();
871 }
872
873 //============================================================================
874
875 /*
876 =============
877 V_Init
878 =============
879 */
880 void V_Init (void)
881 {
882         Cmd_AddCommand ("v_cshift", V_cshift_f);        
883         Cmd_AddCommand ("bf", V_BonusFlash_f);
884         Cmd_AddCommand ("centerview", V_StartPitchDrift);
885
886         Cvar_RegisterVariable (&v_centermove);
887         Cvar_RegisterVariable (&v_centerspeed);
888
889         Cvar_RegisterVariable (&v_iyaw_cycle);
890         Cvar_RegisterVariable (&v_iroll_cycle);
891         Cvar_RegisterVariable (&v_ipitch_cycle);
892         Cvar_RegisterVariable (&v_iyaw_level);
893         Cvar_RegisterVariable (&v_iroll_level);
894         Cvar_RegisterVariable (&v_ipitch_level);
895
896         Cvar_RegisterVariable (&v_idlescale);
897         Cvar_RegisterVariable (&crosshair);
898         Cvar_RegisterVariable (&cl_crossx);
899         Cvar_RegisterVariable (&cl_crossy);
900         Cvar_RegisterVariable (&gl_cshiftpercent);
901
902         Cvar_RegisterVariable (&scr_ofsx);
903         Cvar_RegisterVariable (&scr_ofsy);
904         Cvar_RegisterVariable (&scr_ofsz);
905         Cvar_RegisterVariable (&cl_rollspeed);
906         Cvar_RegisterVariable (&cl_rollangle);
907         Cvar_RegisterVariable (&cl_bob);
908         Cvar_RegisterVariable (&cl_bobcycle);
909         Cvar_RegisterVariable (&cl_bobup);
910
911         Cvar_RegisterVariable (&v_kicktime);
912         Cvar_RegisterVariable (&v_kickroll);
913         Cvar_RegisterVariable (&v_kickpitch);   
914 }
915
916