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 = {0, "cl_rollspeed", "200"};
34 cvar_t cl_rollangle = {0, "cl_rollangle", "2.0"};
36 cvar_t cl_bob = {0, "cl_bob","0.02"};
37 cvar_t cl_bobcycle = {0, "cl_bobcycle","0.6"};
38 cvar_t cl_bobup = {0, "cl_bobup","0.5"};
40 cvar_t v_kicktime = {0, "v_kicktime", "0.5"};
41 cvar_t v_kickroll = {0, "v_kickroll", "0.6"};
42 cvar_t v_kickpitch = {0, "v_kickpitch", "0.6"};
44 cvar_t v_iyaw_cycle = {0, "v_iyaw_cycle", "2"};
45 cvar_t v_iroll_cycle = {0, "v_iroll_cycle", "0.5"};
46 cvar_t v_ipitch_cycle = {0, "v_ipitch_cycle", "1"};
47 cvar_t v_iyaw_level = {0, "v_iyaw_level", "0.3"};
48 cvar_t v_iroll_level = {0, "v_iroll_level", "0.1"};
49 cvar_t v_ipitch_level = {0, "v_ipitch_level", "0.3"};
51 cvar_t v_idlescale = {0, "v_idlescale", "0"};
53 cvar_t crosshair = {CVAR_SAVE, "crosshair", "0"};
55 cvar_t v_centermove = {0, "v_centermove", "0.15"};
56 cvar_t v_centerspeed = {0, "v_centerspeed","500"};
58 float v_dmg_time, v_dmg_roll, v_dmg_pitch;
65 Used by view and sv_user
68 float V_CalcRoll (vec3_t angles, vec3_t velocity)
75 AngleVectors (angles, NULL, right, NULL);
76 side = DotProduct (velocity, right);
77 sign = side < 0 ? -1 : 1;
80 value = cl_rollangle.value;
84 if (side < cl_rollspeed.value)
85 side = side * value / cl_rollspeed.value;
93 static float V_CalcBob (void)
97 // LordHavoc: easy case
98 if (cl_bob.value == 0)
100 if (cl_bobcycle.value == 0)
103 // LordHavoc: FIXME: this code is *weird*, redesign it sometime
104 cycle = cl.time / cl_bobcycle.value;
105 cycle -= (int) cycle;
106 if (cycle < cl_bobup.value)
107 cycle = M_PI * cycle / cl_bobup.value;
109 cycle = M_PI + M_PI*(cycle-cl_bobup.value)/(1.0 - cl_bobup.value);
111 // bob is proportional to velocity in the xy plane
112 // (don't count Z, or jumping messes it up)
114 bob = sqrt(cl.velocity[0]*cl.velocity[0] + cl.velocity[1]*cl.velocity[1]) * cl_bob.value;
115 //Con_Printf ("speed: %5.1f\n", VectorLength(cl.velocity));
116 bob = bob*0.3 + bob*0.7*sin(cycle);
117 bob = bound(-7, bob, 4);
122 void V_StartPitchDrift (void)
125 if (cl.laststop == cl.time)
127 return; // something else is keeping it from drifting
130 if (cl.nodrift || !cl.pitchvel)
132 cl.pitchvel = v_centerspeed.value;
138 void V_StopPitchDrift (void)
140 cl.laststop = cl.time;
149 Moves the client pitch angle towards cl.idealpitch sent by the server.
151 If the user is adjusting pitch manually, either with lookup/lookdown,
152 mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped.
154 Drifting is enabled when the center view key is hit, mlook is released and
155 lookspring is non 0, or when
158 static void V_DriftPitch (void)
162 if (noclip_anglehack || !cl.onground || cls.demoplayback )
169 // don't count small mouse motion
172 if ( fabs(cl.cmd.forwardmove) < cl_forwardspeed.value)
175 cl.driftmove += cl.frametime;
177 if ( cl.driftmove > v_centermove.value)
179 V_StartPitchDrift ();
184 delta = cl.idealpitch - cl.viewangles[PITCH];
192 move = cl.frametime * cl.pitchvel;
193 cl.pitchvel += cl.frametime * v_centerspeed.value;
195 //Con_Printf ("move: %f (%f)\n", move, cl.frametime);
204 cl.viewangles[PITCH] += move;
213 cl.viewangles[PITCH] -= move;
222 ==============================================================================
226 ==============================================================================
235 void V_ParseDamage (void)
238 vec3_t from, forward, right;
242 armor = MSG_ReadByte ();
243 blood = MSG_ReadByte ();
244 for (i=0 ; i<3 ; i++)
245 from[i] = MSG_ReadCoord ();
247 count = blood*0.5 + armor*0.5;
251 cl.faceanimtime = cl.time + 0.2; // put sbar face into pain frame
253 cl.cshifts[CSHIFT_DAMAGE].percent += 3*count;
254 if (cl.cshifts[CSHIFT_DAMAGE].percent < 0)
255 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
256 if (cl.cshifts[CSHIFT_DAMAGE].percent > 150)
257 cl.cshifts[CSHIFT_DAMAGE].percent = 150;
261 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 200;
262 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 100;
263 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 100;
267 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 220;
268 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 50;
269 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 50;
273 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 255;
274 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 0;
275 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 0;
279 // calculate view angle kicks
281 ent = &cl_entities[cl.viewentity];
283 VectorSubtract (from, ent->render.origin, from);
284 VectorNormalize (from);
286 AngleVectors (ent->render.angles, forward, right, NULL);
288 side = DotProduct (from, right);
289 v_dmg_roll = count*side*v_kickroll.value;
291 side = DotProduct (from, forward);
292 v_dmg_pitch = count*side*v_kickpitch.value;
294 v_dmg_time = v_kicktime.value;
297 static cshift_t v_cshift;
304 static void V_cshift_f (void)
306 v_cshift.destcolor[0] = atoi(Cmd_Argv(1));
307 v_cshift.destcolor[1] = atoi(Cmd_Argv(2));
308 v_cshift.destcolor[2] = atoi(Cmd_Argv(3));
309 v_cshift.percent = atoi(Cmd_Argv(4));
317 When you run over an item, the server sends this command
320 static void V_BonusFlash_f (void)
322 cl.cshifts[CSHIFT_BONUS].destcolor[0] = 215;
323 cl.cshifts[CSHIFT_BONUS].destcolor[1] = 186;
324 cl.cshifts[CSHIFT_BONUS].destcolor[2] = 69;
325 cl.cshifts[CSHIFT_BONUS].percent = 50;
333 void V_UpdateBlends (void)
335 float r, g, b, a, a2;
338 if (cl.worldmodel == NULL)
340 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
341 cl.cshifts[CSHIFT_BONUS].percent = 0;
342 cl.cshifts[CSHIFT_CONTENTS].percent = 0;
343 cl.cshifts[CSHIFT_POWERUP].percent = 0;
344 r_refdef.viewblend[0] = 0;
345 r_refdef.viewblend[1] = 0;
346 r_refdef.viewblend[2] = 0;
347 r_refdef.viewblend[3] = 0;
351 // drop the damage value
352 cl.cshifts[CSHIFT_DAMAGE].percent -= (cl.time - cl.oldtime)*150;
353 if (cl.cshifts[CSHIFT_DAMAGE].percent <= 0)
354 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
356 // drop the bonus value
357 cl.cshifts[CSHIFT_BONUS].percent -= (cl.time - cl.oldtime)*100;
358 if (cl.cshifts[CSHIFT_BONUS].percent <= 0)
359 cl.cshifts[CSHIFT_BONUS].percent = 0;
361 // set contents color
362 switch (Mod_PointInLeaf (r_refdef.vieworg, cl.worldmodel)->contents)
366 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = v_cshift.destcolor[0];
367 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = v_cshift.destcolor[1];
368 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = v_cshift.destcolor[2];
369 cl.cshifts[CSHIFT_CONTENTS].percent = v_cshift.percent;
372 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 255;
373 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80;
374 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 0;
375 cl.cshifts[CSHIFT_CONTENTS].percent = 150;
378 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 0;
379 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 25;
380 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 5;
381 cl.cshifts[CSHIFT_CONTENTS].percent = 150;
384 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 130;
385 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80;
386 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 50;
387 cl.cshifts[CSHIFT_CONTENTS].percent = 128;
390 if (cl.items & IT_QUAD)
392 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
393 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 0;
394 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 255;
395 cl.cshifts[CSHIFT_POWERUP].percent = 30;
397 else if (cl.items & IT_SUIT)
399 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
400 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
401 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
402 cl.cshifts[CSHIFT_POWERUP].percent = 20;
404 else if (cl.items & IT_INVISIBILITY)
406 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 100;
407 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 100;
408 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 100;
409 cl.cshifts[CSHIFT_POWERUP].percent = 100;
411 else if (cl.items & IT_INVULNERABILITY)
413 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 255;
414 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
415 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
416 cl.cshifts[CSHIFT_POWERUP].percent = 30;
419 cl.cshifts[CSHIFT_POWERUP].percent = 0;
421 // LordHavoc: fixed V_CalcBlend
427 for (j=0 ; j<NUM_CSHIFTS ; j++)
429 a2 = cl.cshifts[j].percent * (1.0f / 255.0f);
435 r += (cl.cshifts[j].destcolor[0]-r) * a2;
436 g += (cl.cshifts[j].destcolor[1]-g) * a2;
437 b += (cl.cshifts[j].destcolor[2]-b) * a2;
438 a = 1 - (1 - a) * (1 - a2); // correct alpha multiply... took a while to find it on the web
440 // saturate color (to avoid blending in black)
449 r_refdef.viewblend[0] = bound(0, r * (1.0/255.0), 1);
450 r_refdef.viewblend[1] = bound(0, g * (1.0/255.0), 1);
451 r_refdef.viewblend[2] = bound(0, b * (1.0/255.0), 1);
452 r_refdef.viewblend[3] = bound(0, a , 1);
456 ==============================================================================
460 ==============================================================================
470 static void V_AddIdle (float idle)
472 r_refdef.viewangles[ROLL] += idle * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
473 r_refdef.viewangles[PITCH] += idle * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
474 r_refdef.viewangles[YAW] += idle * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
484 void V_CalcRefdef (void)
486 entity_t *ent, *view;
492 if (cls.state != ca_connected || !cl.worldmodel)
495 // ent is the player model (visible when out of body)
496 ent = &cl_entities[cl.viewentity];
497 // view is the weapon model (only visible from inside body)
502 VectorCopy (cl.viewentorigin, r_refdef.vieworg);
504 VectorCopy (cl.viewangles, r_refdef.viewangles);
508 view->render.model = NULL;
509 VectorCopy (ent->render.angles, r_refdef.viewangles);
512 else if (chase_active.value)
514 r_refdef.vieworg[2] += cl.viewheight;
516 V_AddIdle (v_idlescale.value);
520 side = V_CalcRoll (cl_entities[cl.viewentity].render.angles, cl.velocity);
521 r_refdef.viewangles[ROLL] += side;
525 r_refdef.viewangles[ROLL] += v_dmg_time/v_kicktime.value*v_dmg_roll;
526 r_refdef.viewangles[PITCH] += v_dmg_time/v_kicktime.value*v_dmg_pitch;
527 v_dmg_time -= cl.frametime;
530 if (cl.stats[STAT_HEALTH] <= 0)
531 r_refdef.viewangles[ROLL] = 80; // dead view angle
533 V_AddIdle (v_idlescale.value);
536 angles[PITCH] = -ent->render.angles[PITCH]; // because entity pitches are actually backward
537 angles[YAW] = ent->render.angles[YAW];
538 angles[ROLL] = ent->render.angles[ROLL];
540 AngleVectors (angles, forward, NULL, NULL);
544 r_refdef.vieworg[2] += cl.viewheight + bob;
547 view->state_current.modelindex = cl.stats[STAT_WEAPON];
548 view->state_current.frame = cl.stats[STAT_WEAPONFRAME];
549 VectorCopy(r_refdef.vieworg, view->render.origin);
550 //view->render.origin[0] = ent->render.origin[0] + bob * 0.4 * forward[0];
551 //view->render.origin[1] = ent->render.origin[1] + bob * 0.4 * forward[1];
552 //view->render.origin[2] = ent->render.origin[2] + bob * 0.4 * forward[2] + cl.viewheight + bob;
553 view->render.angles[PITCH] = -r_refdef.viewangles[PITCH] - v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
554 view->render.angles[YAW] = r_refdef.viewangles[YAW] - v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
555 view->render.angles[ROLL] = r_refdef.viewangles[ROLL] - v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
556 // FIXME: this setup code is somewhat evil (CL_LerpUpdate should be private?)
558 view->render.colormap = -1; // no special coloring
559 view->render.alpha = ent->render.alpha; // LordHavoc: if the player is transparent, so is the gun
560 view->render.effects = ent->render.effects;
561 view->render.scale = 1.0 / 3.0;
563 // LordHavoc: origin view kick added
566 VectorAdd(r_refdef.viewangles, cl.punchangle, r_refdef.viewangles);
567 VectorAdd(r_refdef.vieworg, cl.punchvector, r_refdef.vieworg);
571 r_refdef.viewent = view->render;
575 //============================================================================
584 Cmd_AddCommand ("v_cshift", V_cshift_f);
585 Cmd_AddCommand ("bf", V_BonusFlash_f);
586 Cmd_AddCommand ("centerview", V_StartPitchDrift);
588 Cvar_RegisterVariable (&v_centermove);
589 Cvar_RegisterVariable (&v_centerspeed);
591 Cvar_RegisterVariable (&v_iyaw_cycle);
592 Cvar_RegisterVariable (&v_iroll_cycle);
593 Cvar_RegisterVariable (&v_ipitch_cycle);
594 Cvar_RegisterVariable (&v_iyaw_level);
595 Cvar_RegisterVariable (&v_iroll_level);
596 Cvar_RegisterVariable (&v_ipitch_level);
598 Cvar_RegisterVariable (&v_idlescale);
599 Cvar_RegisterVariable (&crosshair);
601 Cvar_RegisterVariable (&cl_rollspeed);
602 Cvar_RegisterVariable (&cl_rollangle);
603 Cvar_RegisterVariable (&cl_bob);
604 Cvar_RegisterVariable (&cl_bobcycle);
605 Cvar_RegisterVariable (&cl_bobup);
607 Cvar_RegisterVariable (&v_kicktime);
608 Cvar_RegisterVariable (&v_kickroll);
609 Cvar_RegisterVariable (&v_kickpitch);