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", Length(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 (ent->render.origin, r_refdef.vieworg);
504 VectorCopy (cl.viewangles, r_refdef.viewangles);
508 view->render.model = NULL;
511 else if (chase_active.value)
514 V_AddIdle (v_idlescale.value);
518 side = V_CalcRoll (cl_entities[cl.viewentity].render.angles, cl.velocity);
519 r_refdef.viewangles[ROLL] += side;
523 r_refdef.viewangles[ROLL] += v_dmg_time/v_kicktime.value*v_dmg_roll;
524 r_refdef.viewangles[PITCH] += v_dmg_time/v_kicktime.value*v_dmg_pitch;
525 v_dmg_time -= cl.frametime;
528 if (cl.stats[STAT_HEALTH] <= 0)
529 r_refdef.viewangles[ROLL] = 80; // dead view angle
531 V_AddIdle (v_idlescale.value);
534 angles[PITCH] = -ent->render.angles[PITCH]; // because entity pitches are actually backward
535 angles[YAW] = ent->render.angles[YAW];
536 angles[ROLL] = ent->render.angles[ROLL];
538 AngleVectors (angles, forward, NULL, NULL);
542 r_refdef.vieworg[2] += cl.viewheight + bob;
545 view->state_current.modelindex = cl.stats[STAT_WEAPON];
546 view->state_current.frame = cl.stats[STAT_WEAPONFRAME];
547 view->render.origin[0] = ent->render.origin[0] + bob * 0.4 * forward[0];
548 view->render.origin[1] = ent->render.origin[1] + bob * 0.4 * forward[1];
549 view->render.origin[2] = ent->render.origin[2] + bob * 0.4 * forward[2] + cl.viewheight + bob;
550 view->render.angles[PITCH] = -r_refdef.viewangles[PITCH] - v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
551 view->render.angles[YAW] = r_refdef.viewangles[YAW] - v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
552 view->render.angles[ROLL] = r_refdef.viewangles[ROLL] - v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
553 // FIXME: this setup code is somewhat evil (CL_LerpUpdate should be private?)
555 view->render.colormap = -1; // no special coloring
556 view->render.alpha = ent->render.alpha; // LordHavoc: if the player is transparent, so is the gun
557 view->render.effects = ent->render.effects;
558 view->render.scale = 1;
560 // LordHavoc: origin view kick added
563 VectorAdd(r_refdef.viewangles, cl.punchangle, r_refdef.viewangles);
564 VectorAdd(r_refdef.vieworg, cl.punchvector, r_refdef.vieworg);
568 r_refdef.viewent = view->render;
572 //============================================================================
581 Cmd_AddCommand ("v_cshift", V_cshift_f);
582 Cmd_AddCommand ("bf", V_BonusFlash_f);
583 Cmd_AddCommand ("centerview", V_StartPitchDrift);
585 Cvar_RegisterVariable (&v_centermove);
586 Cvar_RegisterVariable (&v_centerspeed);
588 Cvar_RegisterVariable (&v_iyaw_cycle);
589 Cvar_RegisterVariable (&v_iroll_cycle);
590 Cvar_RegisterVariable (&v_ipitch_cycle);
591 Cvar_RegisterVariable (&v_iyaw_level);
592 Cvar_RegisterVariable (&v_iroll_level);
593 Cvar_RegisterVariable (&v_ipitch_level);
595 Cvar_RegisterVariable (&v_idlescale);
596 Cvar_RegisterVariable (&crosshair);
598 Cvar_RegisterVariable (&cl_rollspeed);
599 Cvar_RegisterVariable (&cl_rollangle);
600 Cvar_RegisterVariable (&cl_bob);
601 Cvar_RegisterVariable (&cl_bobcycle);
602 Cvar_RegisterVariable (&cl_bobup);
604 Cvar_RegisterVariable (&v_kicktime);
605 Cvar_RegisterVariable (&v_kickroll);
606 Cvar_RegisterVariable (&v_kickpitch);