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;
82 if (side < cl_rollspeed.value)
83 side = side * value / cl_rollspeed.value;
91 static float V_CalcBob (void)
95 // LordHavoc: easy case
96 if (cl_bob.value == 0)
98 if (cl_bobcycle.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 bob = bob*0.3 + bob*0.7*sin(cycle);
114 bob = bound(-7, bob, 4);
119 void V_StartPitchDrift (void)
121 if (cl.laststop == cl.time)
122 return; // something else is keeping it from drifting
124 if (cl.nodrift || !cl.pitchvel)
126 cl.pitchvel = v_centerspeed.value;
132 void V_StopPitchDrift (void)
134 cl.laststop = cl.time;
143 Moves the client pitch angle towards cl.idealpitch sent by the server.
145 If the user is adjusting pitch manually, either with lookup/lookdown,
146 mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped.
148 Drifting is enabled when the center view key is hit, mlook is released and
149 lookspring is non 0, or when
152 static void V_DriftPitch (void)
156 if (noclip_anglehack || !cl.onground || cls.demoplayback )
163 // don't count small mouse motion
166 if ( fabs(cl.cmd.forwardmove) < cl_forwardspeed.value)
169 cl.driftmove += cl.frametime;
171 if ( cl.driftmove > v_centermove.value)
173 V_StartPitchDrift ();
178 delta = cl.idealpitch - cl.viewangles[PITCH];
186 move = cl.frametime * cl.pitchvel;
187 cl.pitchvel += cl.frametime * v_centerspeed.value;
196 cl.viewangles[PITCH] += move;
205 cl.viewangles[PITCH] -= move;
211 ==============================================================================
215 ==============================================================================
224 void V_ParseDamage (void)
227 vec3_t from, forward, right;
231 armor = MSG_ReadByte ();
232 blood = MSG_ReadByte ();
233 for (i=0 ; i<3 ; i++)
234 from[i] = MSG_ReadCoord ();
236 count = blood*0.5 + armor*0.5;
240 cl.faceanimtime = cl.time + 0.2; // put sbar face into pain frame
242 cl.cshifts[CSHIFT_DAMAGE].percent += 3*count;
243 if (cl.cshifts[CSHIFT_DAMAGE].percent < 0)
244 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
245 if (cl.cshifts[CSHIFT_DAMAGE].percent > 150)
246 cl.cshifts[CSHIFT_DAMAGE].percent = 150;
250 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 200;
251 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 100;
252 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 100;
256 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 220;
257 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 50;
258 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 50;
262 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 255;
263 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 0;
264 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 0;
268 // calculate view angle kicks
270 ent = &cl_entities[cl.viewentity];
272 VectorSubtract (from, ent->render.origin, from);
273 VectorNormalize (from);
275 AngleVectors (ent->render.angles, forward, right, NULL);
277 side = DotProduct (from, right);
278 v_dmg_roll = count*side*v_kickroll.value;
280 side = DotProduct (from, forward);
281 v_dmg_pitch = count*side*v_kickpitch.value;
283 v_dmg_time = v_kicktime.value;
286 static cshift_t v_cshift;
293 static void V_cshift_f (void)
295 v_cshift.destcolor[0] = atoi(Cmd_Argv(1));
296 v_cshift.destcolor[1] = atoi(Cmd_Argv(2));
297 v_cshift.destcolor[2] = atoi(Cmd_Argv(3));
298 v_cshift.percent = atoi(Cmd_Argv(4));
306 When you run over an item, the server sends this command
309 static void V_BonusFlash_f (void)
311 cl.cshifts[CSHIFT_BONUS].destcolor[0] = 215;
312 cl.cshifts[CSHIFT_BONUS].destcolor[1] = 186;
313 cl.cshifts[CSHIFT_BONUS].destcolor[2] = 69;
314 cl.cshifts[CSHIFT_BONUS].percent = 50;
322 void V_UpdateBlends (void)
324 float r, g, b, a, a2;
327 if (cls.signon != SIGNONS)
329 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
330 cl.cshifts[CSHIFT_BONUS].percent = 0;
331 cl.cshifts[CSHIFT_CONTENTS].percent = 0;
332 cl.cshifts[CSHIFT_POWERUP].percent = 0;
333 r_refdef.viewblend[0] = 0;
334 r_refdef.viewblend[1] = 0;
335 r_refdef.viewblend[2] = 0;
336 r_refdef.viewblend[3] = 0;
340 // drop the damage value
341 cl.cshifts[CSHIFT_DAMAGE].percent -= (cl.time - cl.oldtime)*150;
342 if (cl.cshifts[CSHIFT_DAMAGE].percent <= 0)
343 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
345 // drop the bonus value
346 cl.cshifts[CSHIFT_BONUS].percent -= (cl.time - cl.oldtime)*100;
347 if (cl.cshifts[CSHIFT_BONUS].percent <= 0)
348 cl.cshifts[CSHIFT_BONUS].percent = 0;
350 // set contents color
351 switch (Mod_PointContents (r_refdef.vieworg, cl.worldmodel))
355 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = v_cshift.destcolor[0];
356 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = v_cshift.destcolor[1];
357 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = v_cshift.destcolor[2];
358 cl.cshifts[CSHIFT_CONTENTS].percent = v_cshift.percent;
361 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 255;
362 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80;
363 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 0;
364 cl.cshifts[CSHIFT_CONTENTS].percent = 150 >> 1;
367 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 0;
368 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 25;
369 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 5;
370 cl.cshifts[CSHIFT_CONTENTS].percent = 150 >> 1;
373 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 130;
374 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80;
375 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 50;
376 cl.cshifts[CSHIFT_CONTENTS].percent = 128 >> 1;
379 if (cl.items & IT_QUAD)
381 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
382 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 0;
383 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 255;
384 cl.cshifts[CSHIFT_POWERUP].percent = 30;
386 else if (cl.items & IT_SUIT)
388 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
389 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
390 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
391 cl.cshifts[CSHIFT_POWERUP].percent = 20;
393 else if (cl.items & IT_INVISIBILITY)
395 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 100;
396 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 100;
397 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 100;
398 cl.cshifts[CSHIFT_POWERUP].percent = 100;
400 else if (cl.items & IT_INVULNERABILITY)
402 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 255;
403 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
404 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
405 cl.cshifts[CSHIFT_POWERUP].percent = 30;
408 cl.cshifts[CSHIFT_POWERUP].percent = 0;
410 // LordHavoc: fixed V_CalcBlend
416 for (j=0 ; j<NUM_CSHIFTS ; j++)
418 a2 = cl.cshifts[j].percent * (1.0f / 255.0f);
424 r += (cl.cshifts[j].destcolor[0]-r) * a2;
425 g += (cl.cshifts[j].destcolor[1]-g) * a2;
426 b += (cl.cshifts[j].destcolor[2]-b) * a2;
427 a = 1 - (1 - a) * (1 - a2); // correct alpha multiply... took a while to find it on the web
429 // saturate color (to avoid blending in black)
438 r_refdef.viewblend[0] = bound(0, r * (1.0/255.0), 1);
439 r_refdef.viewblend[1] = bound(0, g * (1.0/255.0), 1);
440 r_refdef.viewblend[2] = bound(0, b * (1.0/255.0), 1);
441 r_refdef.viewblend[3] = bound(0, a , 1);
445 ==============================================================================
449 ==============================================================================
459 static void V_AddIdle (float idle)
461 r_refdef.viewangles[ROLL] += idle * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
462 r_refdef.viewangles[PITCH] += idle * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
463 r_refdef.viewangles[YAW] += idle * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
473 void V_CalcRefdef (void)
475 entity_t *ent, *view;
481 if (cls.state != ca_connected || cls.signon != SIGNONS)
484 // ent is the player model (visible when out of body)
485 ent = &cl_entities[cl.viewentity];
486 // view is the weapon model (only visible from inside body)
491 VectorCopy (cl.viewentorigin, r_refdef.vieworg);
493 VectorCopy (cl.viewangles, r_refdef.viewangles);
497 view->render.model = NULL;
498 VectorCopy (ent->render.angles, r_refdef.viewangles);
501 else if (chase_active.value)
503 r_refdef.vieworg[2] += cl.viewheight;
505 V_AddIdle (v_idlescale.value);
509 side = V_CalcRoll (cl_entities[cl.viewentity].render.angles, cl.velocity);
510 r_refdef.viewangles[ROLL] += side;
514 r_refdef.viewangles[ROLL] += v_dmg_time/v_kicktime.value*v_dmg_roll;
515 r_refdef.viewangles[PITCH] += v_dmg_time/v_kicktime.value*v_dmg_pitch;
516 v_dmg_time -= cl.frametime;
519 if (cl.stats[STAT_HEALTH] <= 0)
520 r_refdef.viewangles[ROLL] = 80; // dead view angle
522 V_AddIdle (v_idlescale.value);
525 angles[PITCH] = -ent->render.angles[PITCH]; // because entity pitches are actually backward
526 angles[YAW] = ent->render.angles[YAW];
527 angles[ROLL] = ent->render.angles[ROLL];
529 AngleVectors (angles, forward, NULL, NULL);
533 r_refdef.vieworg[2] += cl.viewheight + bob;
536 // (FIXME! this should be in cl_main.c with the other linking code, not view.c!)
537 view->state_current.modelindex = cl.stats[STAT_WEAPON];
538 view->state_current.frame = cl.stats[STAT_WEAPONFRAME];
539 VectorCopy(r_refdef.vieworg, view->render.origin);
540 //view->render.origin[0] = ent->render.origin[0] + bob * 0.4 * forward[0];
541 //view->render.origin[1] = ent->render.origin[1] + bob * 0.4 * forward[1];
542 //view->render.origin[2] = ent->render.origin[2] + bob * 0.4 * forward[2] + cl.viewheight + bob;
543 view->render.angles[PITCH] = -r_refdef.viewangles[PITCH] - v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
544 view->render.angles[YAW] = r_refdef.viewangles[YAW] - v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
545 view->render.angles[ROLL] = r_refdef.viewangles[ROLL] - v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
546 // FIXME: this setup code is somewhat evil (CL_LerpUpdate should be private?)
548 CL_BoundingBoxForEntity(&view->render);
549 view->render.colormap = -1; // no special coloring
550 view->render.alpha = ent->render.alpha; // LordHavoc: if the player is transparent, so is the gun
551 view->render.effects = ent->render.effects;
552 view->render.scale = 1.0 / 3.0;
554 // LordHavoc: origin view kick added
557 VectorAdd(r_refdef.viewangles, cl.punchangle, r_refdef.viewangles);
558 VectorAdd(r_refdef.vieworg, cl.punchvector, r_refdef.vieworg);
561 // link into render entities list
562 if (r_refdef.numentities < r_refdef.maxentities && r_drawviewmodel.integer && !chase_active.integer && !envmap && r_drawentities.integer && !(cl.items & IT_INVISIBILITY) && cl.stats[STAT_HEALTH] > 0 && view->render.model != NULL)
563 r_refdef.entities[r_refdef.numentities++] = &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);