one more reminder that nested for(i) loops are bad, mmk?
[divverent/darkplaces.git] / view.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
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.
8
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.
12
13 See the GNU General Public License for more details.
14
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.
18
19 */
20 // view.c -- player eye positioning
21
22 #include "quakedef.h"
23 #include "cl_collision.h"
24
25 /*
26
27 The view is allowed to move slightly from it's true position for bobbing,
28 but if it exceeds 8 pixels linear distance (spherical, not box), the list of
29 entities sent from the server may not include everything in the pvs, especially
30 when crossing a water boudnary.
31
32 */
33
34 cvar_t  cl_rollspeed = {0, "cl_rollspeed", "200"};
35 cvar_t  cl_rollangle = {0, "cl_rollangle", "2.0"};
36
37 cvar_t  cl_bob = {0, "cl_bob","0.02"};
38 cvar_t  cl_bobcycle = {0, "cl_bobcycle","0.6"};
39 cvar_t  cl_bobup = {0, "cl_bobup","0.5"};
40
41 cvar_t  v_kicktime = {0, "v_kicktime", "0.5"};
42 cvar_t  v_kickroll = {0, "v_kickroll", "0.6"};
43 cvar_t  v_kickpitch = {0, "v_kickpitch", "0.6"};
44
45 cvar_t  v_iyaw_cycle = {0, "v_iyaw_cycle", "2"};
46 cvar_t  v_iroll_cycle = {0, "v_iroll_cycle", "0.5"};
47 cvar_t  v_ipitch_cycle = {0, "v_ipitch_cycle", "1"};
48 cvar_t  v_iyaw_level = {0, "v_iyaw_level", "0.3"};
49 cvar_t  v_iroll_level = {0, "v_iroll_level", "0.1"};
50 cvar_t  v_ipitch_level = {0, "v_ipitch_level", "0.3"};
51
52 cvar_t  v_idlescale = {0, "v_idlescale", "0"};
53
54 cvar_t  crosshair = {CVAR_SAVE, "crosshair", "0"};
55
56 cvar_t  v_centermove = {0, "v_centermove", "0.15"};
57 cvar_t  v_centerspeed = {0, "v_centerspeed","500"};
58
59 cvar_t cl_stairsmoothspeed = {CVAR_SAVE, "cl_stairsmoothspeed", "160"};
60
61 float   v_dmg_time, v_dmg_roll, v_dmg_pitch;
62
63
64 /*
65 ===============
66 V_CalcRoll
67
68 Used by view and sv_user
69 ===============
70 */
71 float V_CalcRoll (vec3_t angles, vec3_t velocity)
72 {
73         vec3_t  right;
74         float   sign;
75         float   side;
76         float   value;
77
78         AngleVectors (angles, NULL, right, NULL);
79         side = DotProduct (velocity, right);
80         sign = side < 0 ? -1 : 1;
81         side = fabs(side);
82
83         value = cl_rollangle.value;
84
85         if (side < cl_rollspeed.value)
86                 side = side * value / cl_rollspeed.value;
87         else
88                 side = value;
89
90         return side*sign;
91
92 }
93
94 void V_StartPitchDrift (void)
95 {
96         if (cl.laststop == cl.time)
97                 return;         // something else is keeping it from drifting
98
99         if (cl.nodrift || !cl.pitchvel)
100         {
101                 cl.pitchvel = v_centerspeed.value;
102                 cl.nodrift = false;
103                 cl.driftmove = 0;
104         }
105 }
106
107 void V_StopPitchDrift (void)
108 {
109         cl.laststop = cl.time;
110         cl.nodrift = true;
111         cl.pitchvel = 0;
112 }
113
114 /*
115 ===============
116 V_DriftPitch
117
118 Moves the client pitch angle towards cl.idealpitch sent by the server.
119
120 If the user is adjusting pitch manually, either with lookup/lookdown,
121 mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped.
122
123 Drifting is enabled when the center view key is hit, mlook is released and
124 lookspring is non 0, or when
125 ===============
126 */
127 void V_DriftPitch (void)
128 {
129         float           delta, move;
130
131         if (noclip_anglehack || !cl.onground || cls.demoplayback )
132         {
133                 cl.driftmove = 0;
134                 cl.pitchvel = 0;
135                 return;
136         }
137
138 // don't count small mouse motion
139         if (cl.nodrift)
140         {
141                 if ( fabs(cl.cmd.forwardmove) < cl_forwardspeed.value)
142                         cl.driftmove = 0;
143                 else
144                         cl.driftmove += cl.frametime;
145
146                 if ( cl.driftmove > v_centermove.value)
147                 {
148                         V_StartPitchDrift ();
149                 }
150                 return;
151         }
152
153         delta = cl.idealpitch - cl.viewangles[PITCH];
154
155         if (!delta)
156         {
157                 cl.pitchvel = 0;
158                 return;
159         }
160
161         move = cl.frametime * cl.pitchvel;
162         cl.pitchvel += cl.frametime * v_centerspeed.value;
163
164         if (delta > 0)
165         {
166                 if (move > delta)
167                 {
168                         cl.pitchvel = 0;
169                         move = delta;
170                 }
171                 cl.viewangles[PITCH] += move;
172         }
173         else if (delta < 0)
174         {
175                 if (move > -delta)
176                 {
177                         cl.pitchvel = 0;
178                         move = -delta;
179                 }
180                 cl.viewangles[PITCH] -= move;
181         }
182 }
183
184
185 /*
186 ==============================================================================
187
188                                                 SCREEN FLASHES
189
190 ==============================================================================
191 */
192
193
194 /*
195 ===============
196 V_ParseDamage
197 ===============
198 */
199 void V_ParseDamage (void)
200 {
201         int i, armor, blood;
202         vec3_t from;
203         //vec3_t forward, right;
204         vec3_t localfrom;
205         entity_t *ent;
206         //float side;
207         float count;
208
209         armor = MSG_ReadByte ();
210         blood = MSG_ReadByte ();
211         for (i=0 ; i<3 ; i++)
212                 from[i] = MSG_ReadCoord ();
213
214         count = blood*0.5 + armor*0.5;
215         if (count < 10)
216                 count = 10;
217
218         cl.faceanimtime = cl.time + 0.2;                // put sbar face into pain frame
219
220         cl.cshifts[CSHIFT_DAMAGE].percent += 3*count;
221         if (cl.cshifts[CSHIFT_DAMAGE].percent < 0)
222                 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
223         if (cl.cshifts[CSHIFT_DAMAGE].percent > 150)
224                 cl.cshifts[CSHIFT_DAMAGE].percent = 150;
225
226         if (armor > blood)
227         {
228                 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 200;
229                 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 100;
230                 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 100;
231         }
232         else if (armor)
233         {
234                 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 220;
235                 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 50;
236                 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 50;
237         }
238         else
239         {
240                 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 255;
241                 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 0;
242                 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 0;
243         }
244
245         // calculate view angle kicks
246         if (cl.viewentity >= 0 && cl.viewentity < MAX_EDICTS && cl_entities[cl.viewentity].state_current.active)
247         {
248                 ent = &cl_entities[cl.viewentity];
249                 Matrix4x4_Transform(&ent->render.inversematrix, from, localfrom);
250                 VectorNormalize(localfrom);
251                 v_dmg_pitch = count * localfrom[0] * v_kickpitch.value;
252                 v_dmg_roll = count * localfrom[1] * v_kickroll.value;
253                 v_dmg_time = v_kicktime.value;
254         }
255 }
256
257 static cshift_t v_cshift;
258
259 /*
260 ==================
261 V_cshift_f
262 ==================
263 */
264 static void V_cshift_f (void)
265 {
266         v_cshift.destcolor[0] = atoi(Cmd_Argv(1));
267         v_cshift.destcolor[1] = atoi(Cmd_Argv(2));
268         v_cshift.destcolor[2] = atoi(Cmd_Argv(3));
269         v_cshift.percent = atoi(Cmd_Argv(4));
270 }
271
272
273 /*
274 ==================
275 V_BonusFlash_f
276
277 When you run over an item, the server sends this command
278 ==================
279 */
280 static void V_BonusFlash_f (void)
281 {
282         cl.cshifts[CSHIFT_BONUS].destcolor[0] = 215;
283         cl.cshifts[CSHIFT_BONUS].destcolor[1] = 186;
284         cl.cshifts[CSHIFT_BONUS].destcolor[2] = 69;
285         cl.cshifts[CSHIFT_BONUS].percent = 50;
286 }
287
288 /*
289 ==============================================================================
290
291                                                 VIEW RENDERING
292
293 ==============================================================================
294 */
295
296 extern matrix4x4_t viewmodelmatrix;
297
298 /*
299 ==================
300 V_CalcRefdef
301
302 ==================
303 */
304 void V_CalcRefdef (void)
305 {
306         static float oldz;
307         entity_t *ent;
308         if (cls.state == ca_connected && cls.signon == SIGNONS)
309         {
310                 // ent is the player model (visible when out of body)
311                 ent = &cl_entities[cl.viewentity];
312                 VectorCopy(ent->render.origin, r_refdef.vieworg);
313                 VectorCopy(cl.viewangles, r_refdef.viewangles);
314                 if (oldz < ent->render.origin[2])
315                 {
316                         if (cl.time > cl.oldtime)
317                                 oldz += (cl.time - cl.oldtime) * cl_stairsmoothspeed.value;
318                         oldz -= ent->render.origin[2];
319                         oldz = bound(-16, oldz, 0);
320                         r_refdef.vieworg[2] += oldz;
321                         oldz += ent->render.origin[2];
322                 }
323                 else
324                         oldz = ent->render.origin[2];
325                 if (cl.intermission)
326                 {
327                         // entity is a fixed camera
328                         VectorCopy(ent->render.angles, r_refdef.viewangles);
329                 }
330                 else if (chase_active.value)
331                 {
332                         // observing entity from third person
333                         Chase_Update();
334                 }
335                 else
336                 {
337                         // first person view from entity
338                         // angles
339                         if (cl.stats[STAT_HEALTH] <= 0)
340                                 r_refdef.viewangles[ROLL] = 80; // dead view angle
341                         VectorAdd(r_refdef.viewangles, cl.punchangle, r_refdef.viewangles);
342                         r_refdef.viewangles[ROLL] += V_CalcRoll(cl.viewangles, cl.velocity);
343                         if (v_dmg_time > 0)
344                         {
345                                 r_refdef.viewangles[ROLL] += v_dmg_time/v_kicktime.value*v_dmg_roll;
346                                 r_refdef.viewangles[PITCH] += v_dmg_time/v_kicktime.value*v_dmg_pitch;
347                                 v_dmg_time -= cl.frametime;
348                         }
349                         if (v_idlescale.value)
350                         {
351                                 r_refdef.viewangles[ROLL] += v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
352                                 r_refdef.viewangles[PITCH] += v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
353                                 r_refdef.viewangles[YAW] += v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
354                         }
355                         // origin
356                         VectorAdd(r_refdef.vieworg, cl.punchvector, r_refdef.vieworg);
357                         r_refdef.vieworg[2] += cl.viewheight;
358                         if (cl.stats[STAT_HEALTH] > 0 && cl_bob.value && cl_bobcycle.value)
359                         {
360                                 double bob, cycle;
361                                 // LordHavoc: this code is *weird*, but not replacable (I think it
362                                 // should be done in QC on the server, but oh well, quake is quake)
363                                 // LordHavoc: figured out bobup: the time at which the sin is at 180
364                                 // degrees (which allows lengthening or squishing the peak or valley)
365                                 cycle = cl.time / cl_bobcycle.value;
366                                 cycle -= (int) cycle;
367                                 if (cycle < cl_bobup.value)
368                                         cycle = sin(M_PI * cycle / cl_bobup.value);
369                                 else
370                                         cycle = sin(M_PI + M_PI * (cycle-cl_bobup.value)/(1.0 - cl_bobup.value));
371                                 // bob is proportional to velocity in the xy plane
372                                 // (don't count Z, or jumping messes it up)
373                                 bob = sqrt(cl.velocity[0]*cl.velocity[0] + cl.velocity[1]*cl.velocity[1]) * cl_bob.value;
374                                 bob = bob*0.3 + bob*0.7*cycle;
375                                 r_refdef.vieworg[2] += bound(-7, bob, 4);
376                         }
377                 }
378                 // calculate a viewmodel matrix for use in view-attached entities
379                 Matrix4x4_CreateFromQuakeEntity(&viewmodelmatrix, r_refdef.vieworg[0], r_refdef.vieworg[1], r_refdef.vieworg[2], r_refdef.viewangles[0] + v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value, r_refdef.viewangles[1] - v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value, r_refdef.viewangles[2] - v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value, 0.3);
380         }
381         else
382                 Matrix4x4_CreateIdentity(&viewmodelmatrix);
383 }
384
385 void V_FadeViewFlashs(void)
386 {
387         // drop the damage value
388         cl.cshifts[CSHIFT_DAMAGE].percent -= (cl.time - cl.oldtime)*150;
389         if (cl.cshifts[CSHIFT_DAMAGE].percent <= 0)
390                 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
391         // drop the bonus value
392         cl.cshifts[CSHIFT_BONUS].percent -= (cl.time - cl.oldtime)*100;
393         if (cl.cshifts[CSHIFT_BONUS].percent <= 0)
394                 cl.cshifts[CSHIFT_BONUS].percent = 0;
395 }
396
397 void V_CalcViewBlend(void)
398 {
399         float a2;
400         int j;
401         r_refdef.viewblend[0] = 0;
402         r_refdef.viewblend[1] = 0;
403         r_refdef.viewblend[2] = 0;
404         r_refdef.viewblend[3] = 0;
405         if (cls.state == ca_connected && cls.signon == SIGNONS)
406         {
407                 // set contents color
408                 switch (CL_PointQ1Contents(r_refdef.vieworg))
409                 {
410                 case CONTENTS_EMPTY:
411                 case CONTENTS_SOLID:
412                         cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = v_cshift.destcolor[0];
413                         cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = v_cshift.destcolor[1];
414                         cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = v_cshift.destcolor[2];
415                         cl.cshifts[CSHIFT_CONTENTS].percent = v_cshift.percent;
416                         break;
417                 case CONTENTS_LAVA:
418                         cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 255;
419                         cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80;
420                         cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 0;
421                         cl.cshifts[CSHIFT_CONTENTS].percent = 150 >> 1;
422                         break;
423                 case CONTENTS_SLIME:
424                         cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 0;
425                         cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 25;
426                         cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 5;
427                         cl.cshifts[CSHIFT_CONTENTS].percent = 150 >> 1;
428                         break;
429                 default:
430                         cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 130;
431                         cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80;
432                         cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 50;
433                         cl.cshifts[CSHIFT_CONTENTS].percent = 128 >> 1;
434                 }
435
436                 if (cl.items & IT_QUAD)
437                 {
438                         cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
439                         cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 0;
440                         cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 255;
441                         cl.cshifts[CSHIFT_POWERUP].percent = 30;
442                 }
443                 else if (cl.items & IT_SUIT)
444                 {
445                         cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
446                         cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
447                         cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
448                         cl.cshifts[CSHIFT_POWERUP].percent = 20;
449                 }
450                 else if (cl.items & IT_INVISIBILITY)
451                 {
452                         cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 100;
453                         cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 100;
454                         cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 100;
455                         cl.cshifts[CSHIFT_POWERUP].percent = 100;
456                 }
457                 else if (cl.items & IT_INVULNERABILITY)
458                 {
459                         cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 255;
460                         cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
461                         cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
462                         cl.cshifts[CSHIFT_POWERUP].percent = 30;
463                 }
464                 else
465                         cl.cshifts[CSHIFT_POWERUP].percent = 0;
466
467                 // LordHavoc: fixed V_CalcBlend
468                 for (j = 0;j < NUM_CSHIFTS;j++)
469                 {
470                         a2 = bound(0.0f, cl.cshifts[j].percent * (1.0f / 255.0f), 1.0f);
471                         if (a2 > 0)
472                         {
473                                 VectorLerp(r_refdef.viewblend, a2, cl.cshifts[j].destcolor, r_refdef.viewblend);
474                                 r_refdef.viewblend[3] = 1 - (1 - r_refdef.viewblend[3]) * (1 - a2); // correct alpha multiply...  took a while to find it on the web
475                         }
476                 }
477                 // saturate color (to avoid blending in black)
478                 if (r_refdef.viewblend[3])
479                 {
480                         a2 = 1 / r_refdef.viewblend[3];
481                         VectorScale(r_refdef.viewblend, a2, r_refdef.viewblend);
482                 }
483
484                 r_refdef.viewblend[0] = bound(0.0f, r_refdef.viewblend[0] * (1.0f/255.0f), 1.0f);
485                 r_refdef.viewblend[1] = bound(0.0f, r_refdef.viewblend[1] * (1.0f/255.0f), 1.0f);
486                 r_refdef.viewblend[2] = bound(0.0f, r_refdef.viewblend[2] * (1.0f/255.0f), 1.0f);
487                 r_refdef.viewblend[3] = bound(0.0f, r_refdef.viewblend[3]                , 1.0f);
488         }
489 }
490
491 //============================================================================
492
493 /*
494 =============
495 V_Init
496 =============
497 */
498 void V_Init (void)
499 {
500         Cmd_AddCommand ("v_cshift", V_cshift_f);
501         Cmd_AddCommand ("bf", V_BonusFlash_f);
502         Cmd_AddCommand ("centerview", V_StartPitchDrift);
503
504         Cvar_RegisterVariable (&v_centermove);
505         Cvar_RegisterVariable (&v_centerspeed);
506
507         Cvar_RegisterVariable (&v_iyaw_cycle);
508         Cvar_RegisterVariable (&v_iroll_cycle);
509         Cvar_RegisterVariable (&v_ipitch_cycle);
510         Cvar_RegisterVariable (&v_iyaw_level);
511         Cvar_RegisterVariable (&v_iroll_level);
512         Cvar_RegisterVariable (&v_ipitch_level);
513
514         Cvar_RegisterVariable (&v_idlescale);
515         Cvar_RegisterVariable (&crosshair);
516
517         Cvar_RegisterVariable (&cl_rollspeed);
518         Cvar_RegisterVariable (&cl_rollangle);
519         Cvar_RegisterVariable (&cl_bob);
520         Cvar_RegisterVariable (&cl_bobcycle);
521         Cvar_RegisterVariable (&cl_bobup);
522
523         Cvar_RegisterVariable (&v_kicktime);
524         Cvar_RegisterVariable (&v_kickroll);
525         Cvar_RegisterVariable (&v_kickpitch);
526
527         Cvar_RegisterVariable (&cl_stairsmoothspeed);
528 }
529