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 scr_ofsx = {"scr_ofsx","0", false};
34 cvar_t scr_ofsy = {"scr_ofsy","0", false};
35 cvar_t scr_ofsz = {"scr_ofsz","0", false};
37 cvar_t cl_rollspeed = {"cl_rollspeed", "200"};
38 cvar_t cl_rollangle = {"cl_rollangle", "2.0"};
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};
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};
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};
55 cvar_t v_idlescale = {"v_idlescale", "0", false};
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};
61 cvar_t gl_cshiftpercent = {"gl_cshiftpercent", "100", false};
63 float v_dmg_time, v_dmg_roll, v_dmg_pitch;
65 extern int in_forward, in_forward2, in_back;
72 Used by view and sv_user
75 vec3_t forward, right, up;
77 float V_CalcRoll (vec3_t angles, vec3_t velocity)
83 AngleVectors (angles, forward, right, up);
84 side = DotProduct (velocity, right);
85 sign = side < 0 ? -1 : 1;
88 value = cl_rollangle.value;
92 if (side < cl_rollspeed.value)
93 side = side * value / cl_rollspeed.value;
108 float V_CalcBob (void)
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;
118 cycle = M_PI + M_PI*(cycle-cl_bobup.value)/(1.0 - cl_bobup.value);
120 // bob is proportional to velocity in the xy plane
121 // (don't count Z, or jumping messes it up)
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);
135 //=============================================================================
138 cvar_t v_centermove = {"v_centermove", "0.15", false};
139 cvar_t v_centerspeed = {"v_centerspeed","500"};
142 void V_StartPitchDrift (void)
145 if (cl.laststop == cl.time)
147 return; // something else is keeping it from drifting
150 if (cl.nodrift || !cl.pitchvel)
152 cl.pitchvel = v_centerspeed.value;
158 void V_StopPitchDrift (void)
160 cl.laststop = cl.time;
169 Moves the client pitch angle towards cl.idealpitch sent by the server.
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.
174 Drifting is enabled when the center view key is hit, mlook is released and
175 lookspring is non 0, or when
178 void V_DriftPitch (void)
182 if (noclip_anglehack || !cl.onground || cls.demoplayback )
189 // don't count small mouse motion
192 if ( fabs(cl.cmd.forwardmove) < cl_forwardspeed.value)
195 cl.driftmove += host_frametime;
197 if ( cl.driftmove > v_centermove.value)
199 V_StartPitchDrift ();
204 delta = cl.idealpitch - cl.viewangles[PITCH];
212 move = host_frametime * cl.pitchvel;
213 cl.pitchvel += host_frametime * v_centerspeed.value;
215 //Con_Printf ("move: %f (%f)\n", move, host_frametime);
224 cl.viewangles[PITCH] += move;
233 cl.viewangles[PITCH] -= move;
242 ==============================================================================
246 ==============================================================================
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 };
256 float v_blend[4]; // rgba 0.0 - 1.0
263 void V_ParseDamage (void)
268 vec3_t forward, right, up;
273 armor = MSG_ReadByte ();
274 blood = MSG_ReadByte ();
275 for (i=0 ; i<3 ; i++)
276 from[i] = MSG_ReadCoord ();
278 count = blood*0.5 + armor*0.5;
282 cl.faceanimtime = cl.time + 0.2; // but sbar face into pain frame
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;
292 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 200;
293 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 100;
294 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 100;
298 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 220;
299 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 50;
300 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 50;
304 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 255;
305 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 0;
306 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 0;
310 // calculate view angle kicks
312 ent = &cl_entities[cl.viewentity];
314 VectorSubtract (from, ent->origin, from);
315 VectorNormalize (from);
317 AngleVectors (ent->angles, forward, right, up);
319 side = DotProduct (from, right);
320 v_dmg_roll = count*side*v_kickroll.value;
322 side = DotProduct (from, forward);
323 v_dmg_pitch = count*side*v_kickpitch.value;
325 v_dmg_time = v_kicktime.value;
334 void V_cshift_f (void)
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));
347 When you run over an item, the server sends this command
350 void V_BonusFlash_f (void)
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;
362 Underwater, lava, etc each has a color shift
365 void V_SetContentsColor (int contents)
368 c = &cl.cshifts[CSHIFT_CONTENTS]; // just to shorten the code below
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;
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;
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;
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;
407 void V_CalcPowerupCshift (void)
409 if (cl.items & IT_QUAD)
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;
416 else if (cl.items & IT_SUIT)
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;
423 else if (cl.items & IT_INVISIBILITY)
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;
430 else if (cl.items & IT_INVULNERABILITY)
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;
438 cl.cshifts[CSHIFT_POWERUP].percent = 0;
446 // LordHavoc: fixed V_CalcBlend
447 void V_CalcBlend (void)
449 float r, g, b, a, a2;
457 if (gl_cshiftpercent.value)
459 for (j=0 ; j<NUM_CSHIFTS ; j++)
461 a2 = ((cl.cshifts[j].percent * gl_cshiftpercent.value) / 100.0) / 255.0;
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
472 // saturate color (to avoid blending in black)
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);
493 void V_UpdatePalette (void)
498 V_CalcPowerupCshift ();
502 for (i=0 ; i<NUM_CSHIFTS ; i++)
504 if (cl.cshifts[i].percent != cl.prev_cshifts[i].percent)
507 cl.prev_cshifts[i].percent = cl.cshifts[i].percent;
509 for (j=0 ; j<3 ; j++)
510 if (cl.cshifts[i].destcolor[j] != cl.prev_cshifts[i].destcolor[j])
513 cl.prev_cshifts[i].destcolor[j] = cl.cshifts[i].destcolor[j];
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;
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;
534 ==============================================================================
538 ==============================================================================
541 float angledelta (float a)
554 void CalcGunAngle (void)
556 float yaw, pitch, move;
557 static float oldyaw = 0;
558 static float oldpitch = 0;
560 yaw = r_refdef.viewangles[YAW];
561 pitch = -r_refdef.viewangles[PITCH];
563 yaw = angledelta(yaw - r_refdef.viewangles[YAW]) * 0.4;
568 pitch = angledelta(-pitch - r_refdef.viewangles[PITCH]) * 0.4;
573 move = host_frametime*20;
576 if (oldyaw + move < yaw)
581 if (oldyaw - move > yaw)
585 if (pitch > oldpitch)
587 if (oldpitch + move < pitch)
588 pitch = oldpitch + move;
592 if (oldpitch - move > pitch)
593 pitch = oldpitch - move;
599 cl.viewent.angles[YAW] = r_refdef.viewangles[YAW] + yaw;
600 cl.viewent.angles[PITCH] = - (r_refdef.viewangles[PITCH] + pitch);
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;
612 void V_BoundOffsets (void)
616 ent = &cl_entities[cl.viewentity];
618 // absolutely bound refresh reletive to entity clipping hull
619 // so the view can never be inside a solid wall
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;
642 void V_AddIdle (void)
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;
654 Roll is induced by movement and damage
657 void V_CalcViewRoll (void)
661 side = V_CalcRoll (cl_entities[cl.viewentity].angles, cl.velocity);
662 r_refdef.viewangles[ROLL] += side;
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;
671 if (cl.stats[STAT_HEALTH] <= 0)
673 r_refdef.viewangles[ROLL] = 80; // dead view angle
682 V_CalcIntermissionRefdef
686 void V_CalcIntermissionRefdef (void)
688 entity_t *ent, *view;
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)
696 VectorCopy (ent->origin, r_refdef.vieworg);
697 VectorCopy (ent->angles, r_refdef.viewangles);
700 // allways idle in intermission
701 old = v_idlescale.value;
702 v_idlescale.value = 1;
704 v_idlescale.value = old;
713 void V_CalcRefdef (void)
715 entity_t *ent, *view;
717 vec3_t forward, right, up;
720 static float oldz = 0;
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)
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
739 VectorCopy (ent->origin, r_refdef.vieworg);
740 r_refdef.vieworg[2] += cl.viewheight + bob;
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;
749 VectorCopy (cl.viewangles, r_refdef.viewangles);
754 angles[PITCH] = -ent->angles[PITCH]; // because entity pitches are
756 angles[YAW] = ent->angles[YAW];
757 angles[ROLL] = ent->angles[ROLL];
759 AngleVectors (angles, forward, right, up);
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];
769 // set up gun position
770 VectorCopy (cl.viewangles, view->angles);
774 VectorCopy (ent->origin, view->origin);
775 view->origin[2] += cl.viewheight;
777 for (i=0 ; i<3 ; i++)
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;
783 view->origin[2] += bob;
785 // fudge position around to keep amount of weapon visible
786 // roughly equal with different FOV
789 if (cl.model_precache[cl.stats[STAT_WEAPON]] && strcmp (cl.model_precache[cl.stats[STAT_WEAPON]]->name, "progs/v_shot2.mdl"))
791 // LordHavoc: everyone hates the gun moving around
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;
803 view->model = cl.model_precache[cl.stats[STAT_WEAPON]];
804 view->frame = cl.stats[STAT_WEAPONFRAME];
805 view->colormap = vid.colormap;
807 // set up the refresh position
808 VectorAdd (r_refdef.viewangles, cl.punchangle, r_refdef.viewangles);
810 // smooth out stair step ups
811 if (cl.onground && ent->origin[2] - oldz > 0)
815 steptime = cl.time - cl.oldtime;
817 //FIXME I_Error ("steptime < 0");
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];
829 oldz = ent->origin[2];
831 if (chase_active.value)
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
843 extern vrect_t scr_vrect;
845 void V_RenderView (void)
850 // don't allow cheats in multiplayer
851 if (cl.maxclients > 1)
853 Cvar_Set ("scr_ofsx", "0");
854 Cvar_Set ("scr_ofsy", "0");
855 Cvar_Set ("scr_ofsz", "0");
859 { // intermission / finale rendering
860 V_CalcIntermissionRefdef ();
864 if (!cl.paused /* && (sv.maxclients > 1 || key_dest == key_game) */ )
873 //============================================================================
882 Cmd_AddCommand ("v_cshift", V_cshift_f);
883 Cmd_AddCommand ("bf", V_BonusFlash_f);
884 Cmd_AddCommand ("centerview", V_StartPitchDrift);
886 Cvar_RegisterVariable (&v_centermove);
887 Cvar_RegisterVariable (&v_centerspeed);
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);
896 Cvar_RegisterVariable (&v_idlescale);
897 Cvar_RegisterVariable (&crosshair);
898 Cvar_RegisterVariable (&cl_crossx);
899 Cvar_RegisterVariable (&cl_crossy);
900 Cvar_RegisterVariable (&gl_cshiftpercent);
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);
911 Cvar_RegisterVariable (&v_kicktime);
912 Cvar_RegisterVariable (&v_kickroll);
913 Cvar_RegisterVariable (&v_kickpitch);