2 Copyright (C) 1996-1997 Id Software, Inc.
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.
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.
13 See the GNU General Public License for more details.
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.
20 // view.c -- player eye positioning
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.
33 cvar_t cl_rollspeed = {"cl_rollspeed", "200"};
34 cvar_t cl_rollangle = {"cl_rollangle", "2.0"};
36 cvar_t cl_bob = {"cl_bob","0.02", false};
37 cvar_t cl_bobcycle = {"cl_bobcycle","0.6", false};
38 cvar_t cl_bobup = {"cl_bobup","0.5", false};
40 cvar_t v_kicktime = {"v_kicktime", "0.5", false};
41 cvar_t v_kickroll = {"v_kickroll", "0.6", false};
42 cvar_t v_kickpitch = {"v_kickpitch", "0.6", false};
44 cvar_t v_iyaw_cycle = {"v_iyaw_cycle", "2", false};
45 cvar_t v_iroll_cycle = {"v_iroll_cycle", "0.5", false};
46 cvar_t v_ipitch_cycle = {"v_ipitch_cycle", "1", false};
47 cvar_t v_iyaw_level = {"v_iyaw_level", "0.3", false};
48 cvar_t v_iroll_level = {"v_iroll_level", "0.1", false};
49 cvar_t v_ipitch_level = {"v_ipitch_level", "0.3", false};
51 cvar_t v_idlescale = {"v_idlescale", "0", false};
53 cvar_t crosshair = {"crosshair", "0", true};
54 cvar_t cl_crossx = {"cl_crossx", "0", false};
55 cvar_t cl_crossy = {"cl_crossy", "0", false};
57 cvar_t gl_cshiftpercent = {"gl_cshiftpercent", "100", false};
59 float v_dmg_time, v_dmg_roll, v_dmg_pitch;
61 extern int in_forward, in_forward2, in_back;
68 Used by view and sv_user
71 vec3_t forward, right, up;
73 float V_CalcRoll (vec3_t angles, vec3_t velocity)
79 AngleVectors (angles, forward, right, up);
80 side = DotProduct (velocity, right);
81 sign = side < 0 ? -1 : 1;
84 value = cl_rollangle.value;
88 if (side < cl_rollspeed.value)
89 side = side * value / cl_rollspeed.value;
104 float V_CalcBob (void)
109 cycle = cl.time - (int)(cl.time/cl_bobcycle.value)*cl_bobcycle.value;
110 cycle /= cl_bobcycle.value;
111 if (cycle < cl_bobup.value)
112 cycle = M_PI * cycle / cl_bobup.value;
114 cycle = M_PI + M_PI*(cycle-cl_bobup.value)/(1.0 - cl_bobup.value);
116 // bob is proportional to velocity in the xy plane
117 // (don't count Z, or jumping messes it up)
119 bob = sqrt(cl.velocity[0]*cl.velocity[0] + cl.velocity[1]*cl.velocity[1]) * cl_bob.value;
120 //Con_Printf ("speed: %5.1f\n", Length(cl.velocity));
121 bob = bob*0.3 + bob*0.7*sin(cycle);
131 //=============================================================================
134 cvar_t v_centermove = {"v_centermove", "0.15", false};
135 cvar_t v_centerspeed = {"v_centerspeed","500"};
138 void V_StartPitchDrift (void)
141 if (cl.laststop == cl.time)
143 return; // something else is keeping it from drifting
146 if (cl.nodrift || !cl.pitchvel)
148 cl.pitchvel = v_centerspeed.value;
154 void V_StopPitchDrift (void)
156 cl.laststop = cl.time;
165 Moves the client pitch angle towards cl.idealpitch sent by the server.
167 If the user is adjusting pitch manually, either with lookup/lookdown,
168 mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped.
170 Drifting is enabled when the center view key is hit, mlook is released and
171 lookspring is non 0, or when
174 void V_DriftPitch (void)
178 if (noclip_anglehack || !cl.onground || cls.demoplayback )
185 // don't count small mouse motion
188 if ( fabs(cl.cmd.forwardmove) < cl_forwardspeed.value)
191 cl.driftmove += host_frametime;
193 if ( cl.driftmove > v_centermove.value)
195 V_StartPitchDrift ();
200 delta = cl.idealpitch - cl.viewangles[PITCH];
208 move = host_frametime * cl.pitchvel;
209 cl.pitchvel += host_frametime * v_centerspeed.value;
211 //Con_Printf ("move: %f (%f)\n", move, host_frametime);
220 cl.viewangles[PITCH] += move;
229 cl.viewangles[PITCH] -= move;
238 ==============================================================================
242 ==============================================================================
246 cshift_t cshift_empty = { {130,80,50}, 0 };
247 cshift_t cshift_water = { {130,80,50}, 128 };
248 cshift_t cshift_slime = { {0,25,5}, 150 };
249 cshift_t cshift_lava = { {255,80,0}, 150 };
252 float v_blend[4]; // rgba 0.0 - 1.0
259 void V_ParseDamage (void)
264 vec3_t forward, right, up;
269 armor = MSG_ReadByte ();
270 blood = MSG_ReadByte ();
271 for (i=0 ; i<3 ; i++)
272 from[i] = MSG_ReadCoord ();
274 count = blood*0.5 + armor*0.5;
278 cl.faceanimtime = cl.time + 0.2; // but sbar face into pain frame
280 cl.cshifts[CSHIFT_DAMAGE].percent += 3*count;
281 if (cl.cshifts[CSHIFT_DAMAGE].percent < 0)
282 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
283 if (cl.cshifts[CSHIFT_DAMAGE].percent > 150)
284 cl.cshifts[CSHIFT_DAMAGE].percent = 150;
288 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 200;
289 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 100;
290 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 100;
294 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 220;
295 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 50;
296 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 50;
300 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 255;
301 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 0;
302 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 0;
306 // calculate view angle kicks
308 ent = &cl_entities[cl.viewentity];
310 VectorSubtract (from, ent->origin, from);
311 VectorNormalize (from);
313 AngleVectors (ent->angles, forward, right, up);
315 side = DotProduct (from, right);
316 v_dmg_roll = count*side*v_kickroll.value;
318 side = DotProduct (from, forward);
319 v_dmg_pitch = count*side*v_kickpitch.value;
321 v_dmg_time = v_kicktime.value;
330 void V_cshift_f (void)
332 cshift_empty.destcolor[0] = atoi(Cmd_Argv(1));
333 cshift_empty.destcolor[1] = atoi(Cmd_Argv(2));
334 cshift_empty.destcolor[2] = atoi(Cmd_Argv(3));
335 cshift_empty.percent = atoi(Cmd_Argv(4));
343 When you run over an item, the server sends this command
346 void V_BonusFlash_f (void)
348 cl.cshifts[CSHIFT_BONUS].destcolor[0] = 215;
349 cl.cshifts[CSHIFT_BONUS].destcolor[1] = 186;
350 cl.cshifts[CSHIFT_BONUS].destcolor[2] = 69;
351 cl.cshifts[CSHIFT_BONUS].percent = 50;
358 Underwater, lava, etc each has a color shift
361 void V_SetContentsColor (int contents)
364 c = &cl.cshifts[CSHIFT_CONTENTS]; // just to shorten the code below
369 //cl.cshifts[CSHIFT_CONTENTS] = cshift_empty;
370 c->destcolor[0] = cshift_empty.destcolor[0];
371 c->destcolor[1] = cshift_empty.destcolor[1];
372 c->destcolor[2] = cshift_empty.destcolor[2];
373 c->percent = cshift_empty.percent;
376 //cl.cshifts[CSHIFT_CONTENTS] = cshift_lava;
377 c->destcolor[0] = cshift_lava.destcolor[0];
378 c->destcolor[1] = cshift_lava.destcolor[1];
379 c->destcolor[2] = cshift_lava.destcolor[2];
380 c->percent = cshift_lava.percent;
383 //cl.cshifts[CSHIFT_CONTENTS] = cshift_slime;
384 c->destcolor[0] = cshift_slime.destcolor[0];
385 c->destcolor[1] = cshift_slime.destcolor[1];
386 c->destcolor[2] = cshift_slime.destcolor[2];
387 c->percent = cshift_slime.percent;
390 //cl.cshifts[CSHIFT_CONTENTS] = cshift_water;
391 c->destcolor[0] = cshift_water.destcolor[0];
392 c->destcolor[1] = cshift_water.destcolor[1];
393 c->destcolor[2] = cshift_water.destcolor[2];
394 c->percent = cshift_water.percent;
403 void V_CalcPowerupCshift (void)
405 if (cl.items & IT_QUAD)
407 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
408 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 0;
409 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 255;
410 cl.cshifts[CSHIFT_POWERUP].percent = 30;
412 else if (cl.items & IT_SUIT)
414 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
415 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
416 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
417 cl.cshifts[CSHIFT_POWERUP].percent = 20;
419 else if (cl.items & IT_INVISIBILITY)
421 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 100;
422 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 100;
423 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 100;
424 cl.cshifts[CSHIFT_POWERUP].percent = 100;
426 else if (cl.items & IT_INVULNERABILITY)
428 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 255;
429 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
430 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
431 cl.cshifts[CSHIFT_POWERUP].percent = 30;
434 cl.cshifts[CSHIFT_POWERUP].percent = 0;
442 // LordHavoc: fixed V_CalcBlend
443 void V_CalcBlend (void)
445 float r, g, b, a, a2;
453 if (gl_cshiftpercent.value)
455 for (j=0 ; j<NUM_CSHIFTS ; j++)
457 a2 = ((cl.cshifts[j].percent * gl_cshiftpercent.value) / 100.0) / 255.0;
463 r += (cl.cshifts[j].destcolor[0]-r) * a2;
464 g += (cl.cshifts[j].destcolor[1]-g) * a2;
465 b += (cl.cshifts[j].destcolor[2]-b) * a2;
466 a = 1 - (1 - a) * (1 - a2); // correct alpha multiply... took a while to find it on the web
468 // saturate color (to avoid blending in black)
478 v_blend[0] = bound(0, r * (1.0/255.0), 1);
479 v_blend[1] = bound(0, g * (1.0/255.0), 1);
480 v_blend[2] = bound(0, b * (1.0/255.0), 1);
481 v_blend[3] = bound(0, a , 1);
489 void V_UpdatePalette (void)
494 V_CalcPowerupCshift ();
498 for (i=0 ; i<NUM_CSHIFTS ; i++)
500 if (cl.cshifts[i].percent != cl.prev_cshifts[i].percent)
503 cl.prev_cshifts[i].percent = cl.cshifts[i].percent;
505 for (j=0 ; j<3 ; j++)
506 if (cl.cshifts[i].destcolor[j] != cl.prev_cshifts[i].destcolor[j])
509 cl.prev_cshifts[i].destcolor[j] = cl.cshifts[i].destcolor[j];
513 // drop the damage value
514 cl.cshifts[CSHIFT_DAMAGE].percent -= host_frametime*150;
515 if (cl.cshifts[CSHIFT_DAMAGE].percent <= 0)
516 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
518 // drop the bonus value
519 cl.cshifts[CSHIFT_BONUS].percent -= host_frametime*100;
520 if (cl.cshifts[CSHIFT_BONUS].percent <= 0)
521 cl.cshifts[CSHIFT_BONUS].percent = 0;
530 ==============================================================================
534 ==============================================================================
537 float angledelta (float a)
550 void CalcGunAngle (void)
552 float yaw, pitch, move;
553 static float oldyaw = 0;
554 static float oldpitch = 0;
556 yaw = r_refdef.viewangles[YAW];
557 pitch = -r_refdef.viewangles[PITCH];
559 yaw = angledelta(yaw - r_refdef.viewangles[YAW]) * 0.4;
564 pitch = angledelta(-pitch - r_refdef.viewangles[PITCH]) * 0.4;
569 move = host_frametime*20;
572 if (oldyaw + move < yaw)
577 if (oldyaw - move > yaw)
581 if (pitch > oldpitch)
583 if (oldpitch + move < pitch)
584 pitch = oldpitch + move;
588 if (oldpitch - move > pitch)
589 pitch = oldpitch - move;
595 cl.viewent.angles[YAW] = r_refdef.viewangles[YAW] + yaw;
596 cl.viewent.angles[PITCH] = - (r_refdef.viewangles[PITCH] + pitch);
598 cl.viewent.angles[ROLL] -= v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
599 cl.viewent.angles[PITCH] -= v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
600 cl.viewent.angles[YAW] -= v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
608 void V_BoundOffsets (void)
612 ent = &cl_entities[cl.viewentity];
614 // absolutely bound refresh reletive to entity clipping hull
615 // so the view can never be inside a solid wall
617 if (r_refdef.vieworg[0] < ent->origin[0] - 14)
618 r_refdef.vieworg[0] = ent->origin[0] - 14;
619 else if (r_refdef.vieworg[0] > ent->origin[0] + 14)
620 r_refdef.vieworg[0] = ent->origin[0] + 14;
621 if (r_refdef.vieworg[1] < ent->origin[1] - 14)
622 r_refdef.vieworg[1] = ent->origin[1] - 14;
623 else if (r_refdef.vieworg[1] > ent->origin[1] + 14)
624 r_refdef.vieworg[1] = ent->origin[1] + 14;
625 if (r_refdef.vieworg[2] < ent->origin[2] - 22)
626 r_refdef.vieworg[2] = ent->origin[2] - 22;
627 else if (r_refdef.vieworg[2] > ent->origin[2] + 30)
628 r_refdef.vieworg[2] = ent->origin[2] + 30;
638 void V_AddIdle (void)
640 r_refdef.viewangles[ROLL] += v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
641 r_refdef.viewangles[PITCH] += v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
642 r_refdef.viewangles[YAW] += v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
650 Roll is induced by movement and damage
653 void V_CalcViewRoll (void)
657 side = V_CalcRoll (cl_entities[cl.viewentity].angles, cl.velocity);
658 r_refdef.viewangles[ROLL] += side;
662 r_refdef.viewangles[ROLL] += v_dmg_time/v_kicktime.value*v_dmg_roll;
663 r_refdef.viewangles[PITCH] += v_dmg_time/v_kicktime.value*v_dmg_pitch;
664 v_dmg_time -= host_frametime;
667 if (cl.stats[STAT_HEALTH] <= 0)
669 r_refdef.viewangles[ROLL] = 80; // dead view angle
678 V_CalcIntermissionRefdef
682 void V_CalcIntermissionRefdef (void)
684 entity_t *ent, *view;
687 // ent is the player model (visible when out of body)
688 ent = &cl_entities[cl.viewentity];
689 // view is the weapon model (only visible from inside body)
692 VectorCopy (ent->origin, r_refdef.vieworg);
693 VectorCopy (ent->angles, r_refdef.viewangles);
696 // allways idle in intermission
697 old = v_idlescale.value;
698 v_idlescale.value = 1;
700 v_idlescale.value = old;
709 void V_CalcRefdef (void)
711 entity_t *ent, *view;
713 vec3_t forward, right, up;
716 static float oldz = 0;
720 // ent is the player model (visible when out of body)
721 ent = &cl_entities[cl.viewentity];
722 // view is the weapon model (only visible from inside body)
726 // transform the view offset by the model's matrix to get the offset from
727 // model origin for the view
728 ent->angles[YAW] = cl.viewangles[YAW]; // the model should face the view dir
729 ent->angles[PITCH] = -cl.viewangles[PITCH]; // the model should face the view dir
735 VectorCopy (ent->origin, r_refdef.vieworg);
736 r_refdef.vieworg[2] += cl.viewheight + bob;
738 // never let it sit exactly on a node line, because a water plane can
739 // dissapear when viewed with the eye exactly on it.
740 // the server protocol only specifies to 1/16 pixel, so add 1/32 in each axis
741 r_refdef.vieworg[0] += 1.0/32;
742 r_refdef.vieworg[1] += 1.0/32;
743 r_refdef.vieworg[2] += 1.0/32;
745 VectorCopy (cl.viewangles, r_refdef.viewangles);
750 angles[PITCH] = -ent->angles[PITCH]; // because entity pitches are
752 angles[YAW] = ent->angles[YAW];
753 angles[ROLL] = ent->angles[ROLL];
755 AngleVectors (angles, forward, right, up);
759 // set up gun position
760 VectorCopy (cl.viewangles, view->angles);
764 VectorCopy (ent->origin, view->origin);
765 view->origin[2] += cl.viewheight;
767 for (i=0 ; i<3 ; i++)
769 view->origin[i] += forward[i]*bob*0.4;
770 // view->origin[i] += right[i]*bob*0.4;
771 // view->origin[i] += up[i]*bob*0.8;
773 view->origin[2] += bob;
775 // fudge position around to keep amount of weapon visible
776 // roughly equal with different FOV
779 if (cl.model_precache[cl.stats[STAT_WEAPON]] && strcmp (cl.model_precache[cl.stats[STAT_WEAPON]]->name, "progs/v_shot2.mdl"))
781 // LordHavoc: everyone hates the gun moving around
783 if (scr_viewsize.value == 110)
784 view->origin[2] += 1;
785 else if (scr_viewsize.value == 100)
786 view->origin[2] += 2;
787 else if (scr_viewsize.value == 90)
788 view->origin[2] += 1;
789 else if (scr_viewsize.value == 80)
790 view->origin[2] += 0.5;
793 view->model = cl.model_precache[cl.stats[STAT_WEAPON]];
794 view->frame = cl.stats[STAT_WEAPONFRAME];
795 view->colormap = 0; //vid.colormap;
797 // set up the refresh position
798 VectorAdd (r_refdef.viewangles, cl.punchangle, r_refdef.viewangles);
800 // smooth out stair step ups
801 if (cl.onground && ent->origin[2] - oldz > 0)
805 steptime = cl.time - cl.oldtime;
807 //FIXME I_Error ("steptime < 0");
810 oldz += steptime * 80;
811 if (oldz > ent->origin[2])
812 oldz = ent->origin[2];
813 if (ent->origin[2] - oldz > 12)
814 oldz = ent->origin[2] - 12;
815 r_refdef.vieworg[2] += oldz - ent->origin[2];
816 view->origin[2] += oldz - ent->origin[2];
819 oldz = ent->origin[2];
821 if (chase_active.value)
829 The player's clipping box goes from (-16 -16 -24) to (16 16 32) from
830 the entity origin, so any view position inside that will be valid
833 void V_RenderView (void)
839 { // intermission / finale rendering
840 V_CalcIntermissionRefdef ();
844 if (!cl.paused /* && (sv.maxclients > 1 || key_dest == key_game) */ )
853 //============================================================================
862 Cmd_AddCommand ("v_cshift", V_cshift_f);
863 Cmd_AddCommand ("bf", V_BonusFlash_f);
864 Cmd_AddCommand ("centerview", V_StartPitchDrift);
866 Cvar_RegisterVariable (&v_centermove);
867 Cvar_RegisterVariable (&v_centerspeed);
869 Cvar_RegisterVariable (&v_iyaw_cycle);
870 Cvar_RegisterVariable (&v_iroll_cycle);
871 Cvar_RegisterVariable (&v_ipitch_cycle);
872 Cvar_RegisterVariable (&v_iyaw_level);
873 Cvar_RegisterVariable (&v_iroll_level);
874 Cvar_RegisterVariable (&v_ipitch_level);
876 Cvar_RegisterVariable (&v_idlescale);
877 Cvar_RegisterVariable (&crosshair);
878 Cvar_RegisterVariable (&cl_crossx);
879 Cvar_RegisterVariable (&cl_crossy);
880 Cvar_RegisterVariable (&gl_cshiftpercent);
882 Cvar_RegisterVariable (&cl_rollspeed);
883 Cvar_RegisterVariable (&cl_rollangle);
884 Cvar_RegisterVariable (&cl_bob);
885 Cvar_RegisterVariable (&cl_bobcycle);
886 Cvar_RegisterVariable (&cl_bobup);
888 Cvar_RegisterVariable (&v_kicktime);
889 Cvar_RegisterVariable (&v_kickroll);
890 Cvar_RegisterVariable (&v_kickpitch);