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)
228 //vec3_t forward, right;
234 armor = MSG_ReadByte ();
235 blood = MSG_ReadByte ();
236 for (i=0 ; i<3 ; i++)
237 from[i] = MSG_ReadCoord ();
239 count = blood*0.5 + armor*0.5;
243 cl.faceanimtime = cl.time + 0.2; // put sbar face into pain frame
245 cl.cshifts[CSHIFT_DAMAGE].percent += 3*count;
246 if (cl.cshifts[CSHIFT_DAMAGE].percent < 0)
247 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
248 if (cl.cshifts[CSHIFT_DAMAGE].percent > 150)
249 cl.cshifts[CSHIFT_DAMAGE].percent = 150;
253 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 200;
254 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 100;
255 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 100;
259 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 220;
260 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 50;
261 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 50;
265 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 255;
266 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 0;
267 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 0;
271 // calculate view angle kicks
273 ent = &cl_entities[cl.viewentity];
274 Matrix4x4_Transform(&ent->render.inversematrix, from, localfrom);
275 VectorNormalize(localfrom);
276 v_dmg_pitch = count * localfrom[0] * v_kickpitch.value;
277 v_dmg_roll = count * localfrom[1] * v_kickroll.value;
278 v_dmg_time = v_kicktime.value;
280 //VectorSubtract (from, ent->render.origin, from);
281 //VectorNormalize (from);
283 //AngleVectors (ent->render.angles, forward, right, NULL);
285 //side = DotProduct (from, right);
286 //v_dmg_roll = count*side*v_kickroll.value;
288 //side = DotProduct (from, forward);
289 //v_dmg_pitch = count*side*v_kickpitch.value;
291 //v_dmg_time = v_kicktime.value;
294 static cshift_t v_cshift;
301 static void V_cshift_f (void)
303 v_cshift.destcolor[0] = atoi(Cmd_Argv(1));
304 v_cshift.destcolor[1] = atoi(Cmd_Argv(2));
305 v_cshift.destcolor[2] = atoi(Cmd_Argv(3));
306 v_cshift.percent = atoi(Cmd_Argv(4));
314 When you run over an item, the server sends this command
317 static void V_BonusFlash_f (void)
319 cl.cshifts[CSHIFT_BONUS].destcolor[0] = 215;
320 cl.cshifts[CSHIFT_BONUS].destcolor[1] = 186;
321 cl.cshifts[CSHIFT_BONUS].destcolor[2] = 69;
322 cl.cshifts[CSHIFT_BONUS].percent = 50;
330 void V_UpdateBlends (void)
332 float r, g, b, a, a2;
335 if (cls.signon != SIGNONS)
337 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
338 cl.cshifts[CSHIFT_BONUS].percent = 0;
339 cl.cshifts[CSHIFT_CONTENTS].percent = 0;
340 cl.cshifts[CSHIFT_POWERUP].percent = 0;
341 r_refdef.viewblend[0] = 0;
342 r_refdef.viewblend[1] = 0;
343 r_refdef.viewblend[2] = 0;
344 r_refdef.viewblend[3] = 0;
348 // drop the damage value
349 cl.cshifts[CSHIFT_DAMAGE].percent -= (cl.time - cl.oldtime)*150;
350 if (cl.cshifts[CSHIFT_DAMAGE].percent <= 0)
351 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
353 // drop the bonus value
354 cl.cshifts[CSHIFT_BONUS].percent -= (cl.time - cl.oldtime)*100;
355 if (cl.cshifts[CSHIFT_BONUS].percent <= 0)
356 cl.cshifts[CSHIFT_BONUS].percent = 0;
358 // set contents color
359 switch (cl.worldmodel ? cl.worldmodel->PointContents(cl.worldmodel, r_refdef.vieworg) : CONTENTS_EMPTY)
363 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = v_cshift.destcolor[0];
364 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = v_cshift.destcolor[1];
365 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = v_cshift.destcolor[2];
366 cl.cshifts[CSHIFT_CONTENTS].percent = v_cshift.percent;
369 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 255;
370 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80;
371 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 0;
372 cl.cshifts[CSHIFT_CONTENTS].percent = 150 >> 1;
375 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 0;
376 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 25;
377 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 5;
378 cl.cshifts[CSHIFT_CONTENTS].percent = 150 >> 1;
381 cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 130;
382 cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80;
383 cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 50;
384 cl.cshifts[CSHIFT_CONTENTS].percent = 128 >> 1;
387 if (cl.items & IT_QUAD)
389 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
390 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 0;
391 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 255;
392 cl.cshifts[CSHIFT_POWERUP].percent = 30;
394 else if (cl.items & IT_SUIT)
396 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
397 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
398 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
399 cl.cshifts[CSHIFT_POWERUP].percent = 20;
401 else if (cl.items & IT_INVISIBILITY)
403 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 100;
404 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 100;
405 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 100;
406 cl.cshifts[CSHIFT_POWERUP].percent = 100;
408 else if (cl.items & IT_INVULNERABILITY)
410 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 255;
411 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
412 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
413 cl.cshifts[CSHIFT_POWERUP].percent = 30;
416 cl.cshifts[CSHIFT_POWERUP].percent = 0;
418 // LordHavoc: fixed V_CalcBlend
424 for (j=0 ; j<NUM_CSHIFTS ; j++)
426 a2 = cl.cshifts[j].percent * (1.0f / 255.0f);
432 r += (cl.cshifts[j].destcolor[0]-r) * a2;
433 g += (cl.cshifts[j].destcolor[1]-g) * a2;
434 b += (cl.cshifts[j].destcolor[2]-b) * a2;
435 a = 1 - (1 - a) * (1 - a2); // correct alpha multiply... took a while to find it on the web
437 // saturate color (to avoid blending in black)
446 r_refdef.viewblend[0] = bound(0, r * (1.0/255.0), 1);
447 r_refdef.viewblend[1] = bound(0, g * (1.0/255.0), 1);
448 r_refdef.viewblend[2] = bound(0, b * (1.0/255.0), 1);
449 r_refdef.viewblend[3] = bound(0, a , 1);
453 ==============================================================================
457 ==============================================================================
467 static void V_AddIdle (float idle)
469 r_refdef.viewangles[ROLL] += idle * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
470 r_refdef.viewangles[PITCH] += idle * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
471 r_refdef.viewangles[YAW] += idle * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
474 #define MAXVIEWMODELS 32
475 extern int numviewmodels;
476 extern entity_t *viewmodels[MAXVIEWMODELS];
477 void V_LinkViewEntities(void)
482 matrix4x4_t matrix, matrix2;
484 if (numviewmodels <= 0)
487 //Matrix4x4_CreateRotate(&matrix, 1, 0, 0, r_refdef.viewangles[0]);
488 //Matrix4x4_CreateRotate(&matrix, 0, 1, 0, r_refdef.viewangles[0]);
489 //Matrix4x4_CreateRotate(&matrix, 0, 0, 1, r_refdef.viewangles[0]);
490 Matrix4x4_CreateFromQuakeEntity(&matrix, r_refdef.vieworg[0], r_refdef.vieworg[1], r_refdef.vieworg[2], r_refdef.viewangles[0], r_refdef.viewangles[1], r_refdef.viewangles[2], 0.3);
491 for (i = 0;i < numviewmodels && r_refdef.numentities < r_refdef.maxentities;i++)
494 r_refdef.entities[r_refdef.numentities++] = &ent->render;
496 //VectorCopy(ent->render.origin, v);
497 //ent->render.origin[0] = v[0] * vpn[0] + v[1] * vright[0] + v[2] * vup[0] + r_refdef.vieworg[0];
498 //ent->render.origin[1] = v[0] * vpn[1] + v[1] * vright[1] + v[2] * vup[1] + r_refdef.vieworg[1];
499 //ent->render.origin[2] = v[0] * vpn[2] + v[1] * vright[2] + v[2] * vup[2] + r_refdef.vieworg[2];
500 //ent->render.angles[0] = ent->render.angles[0] + r_refdef.viewangles[0];
501 //ent->render.angles[1] = ent->render.angles[1] + r_refdef.viewangles[1];
502 //ent->render.angles[2] = ent->render.angles[2] + r_refdef.viewangles[2];
503 //ent->render.scale *= 0.3;
505 //Matrix4x4_CreateFromQuakeEntity(&ent->render.matrix, ent->render.origin[0], ent->render.origin[1], ent->render.origin[2], ent->render.angles[0], ent->render.angles[1], ent->render.angles[2], ent->render.scale);
506 matrix2 = ent->render.matrix;
507 Matrix4x4_Concat(&ent->render.matrix, &matrix, &matrix2);
508 Matrix4x4_Invert_Simple(&ent->render.inversematrix, &ent->render.matrix);
509 CL_BoundingBoxForEntity(&ent->render);
519 void V_CalcRefdef (void)
521 entity_t *ent, *view;
523 if (cls.state != ca_connected || cls.signon != SIGNONS)
526 // ent is the player model (visible when out of body)
527 ent = &cl_entities[cl.viewentity];
528 // view is the weapon model (only visible from inside body)
533 VectorCopy (ent->render.origin, r_refdef.vieworg);
535 VectorCopy (cl.viewangles, r_refdef.viewangles);
539 view->render.model = NULL;
540 VectorCopy (ent->render.angles, r_refdef.viewangles);
543 else if (chase_active.value)
545 view->render.model = NULL;
546 r_refdef.vieworg[2] += cl.viewheight;
548 V_AddIdle (v_idlescale.value);
552 r_refdef.viewangles[ROLL] += V_CalcRoll (cl.viewangles, cl.velocity);
556 r_refdef.viewangles[ROLL] += v_dmg_time/v_kicktime.value*v_dmg_roll;
557 r_refdef.viewangles[PITCH] += v_dmg_time/v_kicktime.value*v_dmg_pitch;
558 v_dmg_time -= cl.frametime;
561 if (cl.stats[STAT_HEALTH] <= 0)
562 r_refdef.viewangles[ROLL] = 80; // dead view angle
564 V_AddIdle (v_idlescale.value);
566 r_refdef.vieworg[2] += cl.viewheight + V_CalcBob ();
568 // LordHavoc: origin view kick added
571 VectorAdd(r_refdef.viewangles, cl.punchangle, r_refdef.viewangles);
572 VectorAdd(r_refdef.vieworg, cl.punchvector, r_refdef.vieworg);
576 // (FIXME! this should be in cl_main.c with the other linking code, not view.c!)
577 view->state_current.modelindex = cl.stats[STAT_WEAPON];
578 view->state_current.frame = cl.stats[STAT_WEAPONFRAME];
579 //VectorCopy(r_refdef.vieworg, view->render.origin);
580 //view->render.angles[PITCH] = r_refdef.viewangles[PITCH] + v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
581 //view->render.angles[YAW] = r_refdef.viewangles[YAW] - v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
582 //view->render.angles[ROLL] = r_refdef.viewangles[ROLL] - v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
583 //view->render.scale = 1.0 / 3.0;
584 Matrix4x4_CreateFromQuakeEntity(&view->render.matrix, r_refdef.vieworg[0], r_refdef.vieworg[1], r_refdef.vieworg[2], r_refdef.viewangles[PITCH] + v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value, r_refdef.viewangles[YAW] - v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value, r_refdef.viewangles[ROLL] - v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value, 0.3);
585 Matrix4x4_Invert_Simple(&view->render.inversematrix, &view->render.matrix);
586 CL_BoundingBoxForEntity(&view->render);
587 // FIXME: this setup code is somewhat evil (CL_LerpUpdate should be private?)
589 view->render.colormap = -1; // no special coloring
590 view->render.alpha = ent->render.alpha; // LordHavoc: if the player is transparent, so is the gun
591 view->render.effects = ent->render.effects;
592 AngleVectors(r_refdef.viewangles, vpn, vright, vup);
594 // link into render entities list
595 if (r_drawviewmodel.integer && !chase_active.integer && !envmap && r_drawentities.integer && !(cl.items & IT_INVISIBILITY) && cl.stats[STAT_HEALTH] > 0)
597 if (r_refdef.numentities < r_refdef.maxentities && view->render.model != NULL)
598 r_refdef.entities[r_refdef.numentities++] = &view->render;
599 V_LinkViewEntities();
604 //============================================================================
613 Cmd_AddCommand ("v_cshift", V_cshift_f);
614 Cmd_AddCommand ("bf", V_BonusFlash_f);
615 Cmd_AddCommand ("centerview", V_StartPitchDrift);
617 Cvar_RegisterVariable (&v_centermove);
618 Cvar_RegisterVariable (&v_centerspeed);
620 Cvar_RegisterVariable (&v_iyaw_cycle);
621 Cvar_RegisterVariable (&v_iroll_cycle);
622 Cvar_RegisterVariable (&v_ipitch_cycle);
623 Cvar_RegisterVariable (&v_iyaw_level);
624 Cvar_RegisterVariable (&v_iroll_level);
625 Cvar_RegisterVariable (&v_ipitch_level);
627 Cvar_RegisterVariable (&v_idlescale);
628 Cvar_RegisterVariable (&crosshair);
630 Cvar_RegisterVariable (&cl_rollspeed);
631 Cvar_RegisterVariable (&cl_rollangle);
632 Cvar_RegisterVariable (&cl_bob);
633 Cvar_RegisterVariable (&cl_bobcycle);
634 Cvar_RegisterVariable (&cl_bobup);
636 Cvar_RegisterVariable (&v_kicktime);
637 Cvar_RegisterVariable (&v_kickroll);
638 Cvar_RegisterVariable (&v_kickpitch);