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)
101 // LordHavoc: FIXME: this code is *weird*, redesign it sometime
102 cycle = cl.time / cl_bobcycle.value;
103 cycle -= (int) cycle;
104 if (cycle < cl_bobup.value)
105 cycle = M_PI * cycle / cl_bobup.value;
107 cycle = M_PI + M_PI*(cycle-cl_bobup.value)/(1.0 - cl_bobup.value);
109 // bob is proportional to velocity in the xy plane
110 // (don't count Z, or jumping messes it up)
112 bob = sqrt(cl.velocity[0]*cl.velocity[0] + cl.velocity[1]*cl.velocity[1]) * cl_bob.value;
113 //Con_Printf ("speed: %5.1f\n", Length(cl.velocity));
114 bob = bob*0.3 + bob*0.7*sin(cycle);
115 bob = bound(-7, bob, 4);
120 void V_StartPitchDrift (void)
123 if (cl.laststop == cl.time)
125 return; // something else is keeping it from drifting
128 if (cl.nodrift || !cl.pitchvel)
130 cl.pitchvel = v_centerspeed.value;
136 void V_StopPitchDrift (void)
138 cl.laststop = cl.time;
147 Moves the client pitch angle towards cl.idealpitch sent by the server.
149 If the user is adjusting pitch manually, either with lookup/lookdown,
150 mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped.
152 Drifting is enabled when the center view key is hit, mlook is released and
153 lookspring is non 0, or when
156 static void V_DriftPitch (void)
160 if (noclip_anglehack || !cl.onground || cls.demoplayback )
167 // don't count small mouse motion
170 if ( fabs(cl.cmd.forwardmove) < cl_forwardspeed.value)
173 cl.driftmove += cl.frametime;
175 if ( cl.driftmove > v_centermove.value)
177 V_StartPitchDrift ();
182 delta = cl.idealpitch - cl.viewangles[PITCH];
190 move = cl.frametime * cl.pitchvel;
191 cl.pitchvel += cl.frametime * v_centerspeed.value;
193 //Con_Printf ("move: %f (%f)\n", move, cl.frametime);
202 cl.viewangles[PITCH] += move;
211 cl.viewangles[PITCH] -= move;
220 ==============================================================================
224 ==============================================================================
233 void V_ParseDamage (void)
236 vec3_t from, forward, right;
240 armor = MSG_ReadByte ();
241 blood = MSG_ReadByte ();
242 for (i=0 ; i<3 ; i++)
243 from[i] = MSG_ReadCoord ();
245 count = blood*0.5 + armor*0.5;
249 cl.faceanimtime = cl.time + 0.2; // put sbar face into pain frame
251 cl.cshifts[CSHIFT_DAMAGE].percent += 3*count;
252 if (cl.cshifts[CSHIFT_DAMAGE].percent < 0)
253 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
254 if (cl.cshifts[CSHIFT_DAMAGE].percent > 150)
255 cl.cshifts[CSHIFT_DAMAGE].percent = 150;
259 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 200;
260 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 100;
261 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 100;
265 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 220;
266 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 50;
267 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 50;
271 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 255;
272 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 0;
273 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 0;
277 // calculate view angle kicks
279 ent = &cl_entities[cl.viewentity];
281 VectorSubtract (from, ent->render.origin, from);
282 VectorNormalize (from);
284 AngleVectors (ent->render.angles, forward, right, NULL);
286 side = DotProduct (from, right);
287 v_dmg_roll = count*side*v_kickroll.value;
289 side = DotProduct (from, forward);
290 v_dmg_pitch = count*side*v_kickpitch.value;
292 v_dmg_time = v_kicktime.value;
295 static cshift_t v_cshift;
302 static void V_cshift_f (void)
304 v_cshift.destcolor[0] = atoi(Cmd_Argv(1));
305 v_cshift.destcolor[1] = atoi(Cmd_Argv(2));
306 v_cshift.destcolor[2] = atoi(Cmd_Argv(3));
307 v_cshift.percent = atoi(Cmd_Argv(4));
315 When you run over an item, the server sends this command
318 static void V_BonusFlash_f (void)
320 cl.cshifts[CSHIFT_BONUS].destcolor[0] = 215;
321 cl.cshifts[CSHIFT_BONUS].destcolor[1] = 186;
322 cl.cshifts[CSHIFT_BONUS].destcolor[2] = 69;
323 cl.cshifts[CSHIFT_BONUS].percent = 50;
331 void V_UpdateBlends (void)
333 float r, g, b, a, a2;
336 if (cl.worldmodel == NULL)
338 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
339 cl.cshifts[CSHIFT_BONUS].percent = 0;
340 cl.cshifts[CSHIFT_CONTENTS].percent = 0;
341 cl.cshifts[CSHIFT_POWERUP].percent = 0;
342 r_refdef.viewblend[0] = 0;
343 r_refdef.viewblend[1] = 0;
344 r_refdef.viewblend[2] = 0;
345 r_refdef.viewblend[3] = 0;
349 // drop the damage value
350 cl.cshifts[CSHIFT_DAMAGE].percent -= (cl.time - cl.oldtime)*150;
351 if (cl.cshifts[CSHIFT_DAMAGE].percent <= 0)
352 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
354 // drop the bonus value
355 cl.cshifts[CSHIFT_BONUS].percent -= (cl.time - cl.oldtime)*100;
356 if (cl.cshifts[CSHIFT_BONUS].percent <= 0)
357 cl.cshifts[CSHIFT_BONUS].percent = 0;
359 // set contents color
360 switch (Mod_PointInLeaf (r_refdef.vieworg, cl.worldmodel)->contents)
364 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = v_cshift.destcolor[0];
365 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = v_cshift.destcolor[1];
366 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = v_cshift.destcolor[2];
367 cl.cshifts[CSHIFT_CONTENTS].percent = v_cshift.percent;
370 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 255;
371 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80;
372 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 0;
373 cl.cshifts[CSHIFT_CONTENTS].percent = 150;
376 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 0;
377 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 25;
378 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 5;
379 cl.cshifts[CSHIFT_CONTENTS].percent = 150;
382 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 130;
383 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80;
384 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 50;
385 cl.cshifts[CSHIFT_CONTENTS].percent = 128;
388 if (cl.items & IT_QUAD)
390 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
391 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 0;
392 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 255;
393 cl.cshifts[CSHIFT_POWERUP].percent = 30;
395 else if (cl.items & IT_SUIT)
397 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
398 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
399 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
400 cl.cshifts[CSHIFT_POWERUP].percent = 20;
402 else if (cl.items & IT_INVISIBILITY)
404 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 100;
405 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 100;
406 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 100;
407 cl.cshifts[CSHIFT_POWERUP].percent = 100;
409 else if (cl.items & IT_INVULNERABILITY)
411 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 255;
412 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
413 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
414 cl.cshifts[CSHIFT_POWERUP].percent = 30;
417 cl.cshifts[CSHIFT_POWERUP].percent = 0;
419 // LordHavoc: fixed V_CalcBlend
425 for (j=0 ; j<NUM_CSHIFTS ; j++)
427 a2 = cl.cshifts[j].percent * (1.0f / 255.0f);
433 r += (cl.cshifts[j].destcolor[0]-r) * a2;
434 g += (cl.cshifts[j].destcolor[1]-g) * a2;
435 b += (cl.cshifts[j].destcolor[2]-b) * a2;
436 a = 1 - (1 - a) * (1 - a2); // correct alpha multiply... took a while to find it on the web
438 // saturate color (to avoid blending in black)
447 r_refdef.viewblend[0] = bound(0, r * (1.0/255.0), 1);
448 r_refdef.viewblend[1] = bound(0, g * (1.0/255.0), 1);
449 r_refdef.viewblend[2] = bound(0, b * (1.0/255.0), 1);
450 r_refdef.viewblend[3] = bound(0, a , 1);
454 ==============================================================================
458 ==============================================================================
468 static void V_AddIdle (float idle)
470 r_refdef.viewangles[ROLL] += idle * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
471 r_refdef.viewangles[PITCH] += idle * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
472 r_refdef.viewangles[YAW] += idle * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
482 void V_CalcRefdef (void)
484 entity_t *ent, *view;
490 // ent is the player model (visible when out of body)
491 ent = &cl_entities[cl.viewentity];
492 // view is the weapon model (only visible from inside body)
497 VectorCopy (ent->render.origin, r_refdef.vieworg);
499 VectorCopy (cl.viewangles, r_refdef.viewangles);
503 view->render.model = NULL;
506 else if (chase_active.value)
509 V_AddIdle (v_idlescale.value);
513 side = V_CalcRoll (cl_entities[cl.viewentity].render.angles, cl.velocity);
514 r_refdef.viewangles[ROLL] += side;
518 r_refdef.viewangles[ROLL] += v_dmg_time/v_kicktime.value*v_dmg_roll;
519 r_refdef.viewangles[PITCH] += v_dmg_time/v_kicktime.value*v_dmg_pitch;
520 v_dmg_time -= cl.frametime;
523 if (cl.stats[STAT_HEALTH] <= 0)
524 r_refdef.viewangles[ROLL] = 80; // dead view angle
526 V_AddIdle (v_idlescale.value);
529 angles[PITCH] = -ent->render.angles[PITCH]; // because entity pitches are actually backward
530 angles[YAW] = ent->render.angles[YAW];
531 angles[ROLL] = ent->render.angles[ROLL];
533 AngleVectors (angles, forward, NULL, NULL);
537 r_refdef.vieworg[2] += cl.viewheight + bob;
540 view->state_current.modelindex = cl.stats[STAT_WEAPON];
541 view->state_current.frame = cl.stats[STAT_WEAPONFRAME];
542 view->render.origin[0] = ent->render.origin[0] + bob * 0.4 * forward[0];
543 view->render.origin[1] = ent->render.origin[1] + bob * 0.4 * forward[1];
544 view->render.origin[2] = ent->render.origin[2] + bob * 0.4 * forward[2] + cl.viewheight + bob;
545 view->render.angles[PITCH] = -r_refdef.viewangles[PITCH] - v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
546 view->render.angles[YAW] = r_refdef.viewangles[YAW] - v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
547 view->render.angles[ROLL] = r_refdef.viewangles[ROLL] - v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
548 // FIXME: this setup code is somewhat evil (CL_LerpUpdate should be private?)
550 view->render.colormap = -1; // no special coloring
551 view->render.alpha = ent->render.alpha; // LordHavoc: if the player is transparent, so is the gun
552 view->render.effects = ent->render.effects;
553 view->render.scale = 1;
555 // LordHavoc: origin view kick added
558 VectorAdd(r_refdef.viewangles, cl.punchangle, r_refdef.viewangles);
559 VectorAdd(r_refdef.vieworg, cl.punchvector, r_refdef.vieworg);
563 r_refdef.viewent = view->render;
567 //============================================================================
576 Cmd_AddCommand ("v_cshift", V_cshift_f);
577 Cmd_AddCommand ("bf", V_BonusFlash_f);
578 Cmd_AddCommand ("centerview", V_StartPitchDrift);
580 Cvar_RegisterVariable (&v_centermove);
581 Cvar_RegisterVariable (&v_centerspeed);
583 Cvar_RegisterVariable (&v_iyaw_cycle);
584 Cvar_RegisterVariable (&v_iroll_cycle);
585 Cvar_RegisterVariable (&v_ipitch_cycle);
586 Cvar_RegisterVariable (&v_iyaw_level);
587 Cvar_RegisterVariable (&v_iroll_level);
588 Cvar_RegisterVariable (&v_ipitch_level);
590 Cvar_RegisterVariable (&v_idlescale);
591 Cvar_RegisterVariable (&crosshair);
593 Cvar_RegisterVariable (&cl_rollspeed);
594 Cvar_RegisterVariable (&cl_rollangle);
595 Cvar_RegisterVariable (&cl_bob);
596 Cvar_RegisterVariable (&cl_bobcycle);
597 Cvar_RegisterVariable (&cl_bobup);
599 Cvar_RegisterVariable (&v_kicktime);
600 Cvar_RegisterVariable (&v_kickroll);
601 Cvar_RegisterVariable (&v_kickpitch);