added cl_screen.c/h (eventually most 2D stuff should be moved here)
[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
24 /*
25
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.
30
31 */
32
33 cvar_t  cl_rollspeed = {0, "cl_rollspeed", "200"};
34 cvar_t  cl_rollangle = {0, "cl_rollangle", "2.0"};
35
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"};
39
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"};
43
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"};
50
51 cvar_t  v_idlescale = {0, "v_idlescale", "0"};
52
53 cvar_t  crosshair = {CVAR_SAVE, "crosshair", "0"};
54
55 cvar_t  v_centermove = {0, "v_centermove", "0.15"};
56 cvar_t  v_centerspeed = {0, "v_centerspeed","500"};
57
58 float   v_dmg_time, v_dmg_roll, v_dmg_pitch;
59
60
61 /*
62 ===============
63 V_CalcRoll
64
65 Used by view and sv_user
66 ===============
67 */
68 float V_CalcRoll (vec3_t angles, vec3_t velocity)
69 {
70         vec3_t  right;
71         float   sign;
72         float   side;
73         float   value;
74         
75         AngleVectors (angles, NULL, right, NULL);
76         side = DotProduct (velocity, right);
77         sign = side < 0 ? -1 : 1;
78         side = fabs(side);
79         
80         value = cl_rollangle.value;
81 //      if (cl.inwater)
82 //              value *= 6;
83
84         if (side < cl_rollspeed.value)
85                 side = side * value / cl_rollspeed.value;
86         else
87                 side = value;
88
89         return side*sign;
90
91 }
92
93 static float V_CalcBob (void)
94 {
95         double bob, cycle;
96
97         // LordHavoc: easy case
98         if (cl_bob.value == 0)
99                 return 0;
100
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;
106         else
107                 cycle = M_PI + M_PI*(cycle-cl_bobup.value)/(1.0 - cl_bobup.value);
108
109         // bob is proportional to velocity in the xy plane
110         // (don't count Z, or jumping messes it up)
111
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);
116         return bob;
117
118 }
119
120 void V_StartPitchDrift (void)
121 {
122 #if 1
123         if (cl.laststop == cl.time)
124         {
125                 return;         // something else is keeping it from drifting
126         }
127 #endif
128         if (cl.nodrift || !cl.pitchvel)
129         {
130                 cl.pitchvel = v_centerspeed.value;
131                 cl.nodrift = false;
132                 cl.driftmove = 0;
133         }
134 }
135
136 void V_StopPitchDrift (void)
137 {
138         cl.laststop = cl.time;
139         cl.nodrift = true;
140         cl.pitchvel = 0;
141 }
142
143 /*
144 ===============
145 V_DriftPitch
146
147 Moves the client pitch angle towards cl.idealpitch sent by the server.
148
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.
151
152 Drifting is enabled when the center view key is hit, mlook is released and
153 lookspring is non 0, or when
154 ===============
155 */
156 static void V_DriftPitch (void)
157 {
158         float           delta, move;
159
160         if (noclip_anglehack || !cl.onground || cls.demoplayback )
161         {
162                 cl.driftmove = 0;
163                 cl.pitchvel = 0;
164                 return;
165         }
166
167 // don't count small mouse motion
168         if (cl.nodrift)
169         {
170                 if ( fabs(cl.cmd.forwardmove) < cl_forwardspeed.value)
171                         cl.driftmove = 0;
172                 else
173                         cl.driftmove += cl.frametime;
174
175                 if ( cl.driftmove > v_centermove.value)
176                 {
177                         V_StartPitchDrift ();
178                 }
179                 return;
180         }
181
182         delta = cl.idealpitch - cl.viewangles[PITCH];
183
184         if (!delta)
185         {
186                 cl.pitchvel = 0;
187                 return;
188         }
189
190         move = cl.frametime * cl.pitchvel;
191         cl.pitchvel += cl.frametime * v_centerspeed.value;
192
193 //Con_Printf ("move: %f (%f)\n", move, cl.frametime);
194
195         if (delta > 0)
196         {
197                 if (move > delta)
198                 {
199                         cl.pitchvel = 0;
200                         move = delta;
201                 }
202                 cl.viewangles[PITCH] += move;
203         }
204         else if (delta < 0)
205         {
206                 if (move > -delta)
207                 {
208                         cl.pitchvel = 0;
209                         move = -delta;
210                 }
211                 cl.viewangles[PITCH] -= move;
212         }
213 }
214
215
216
217
218
219 /*
220 ==============================================================================
221
222                                                 SCREEN FLASHES
223
224 ==============================================================================
225 */
226
227
228 /*
229 ===============
230 V_ParseDamage
231 ===============
232 */
233 void V_ParseDamage (void)
234 {
235         int i, armor, blood;
236         vec3_t from, forward, right;
237         entity_t *ent;
238         float side, count;
239
240         armor = MSG_ReadByte ();
241         blood = MSG_ReadByte ();
242         for (i=0 ; i<3 ; i++)
243                 from[i] = MSG_ReadCoord ();
244
245         count = blood*0.5 + armor*0.5;
246         if (count < 10)
247                 count = 10;
248
249         cl.faceanimtime = cl.time + 0.2;                // put sbar face into pain frame
250
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;
256
257         if (armor > blood)
258         {
259                 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 200;
260                 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 100;
261                 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 100;
262         }
263         else if (armor)
264         {
265                 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 220;
266                 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 50;
267                 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 50;
268         }
269         else
270         {
271                 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 255;
272                 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 0;
273                 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 0;
274         }
275
276 //
277 // calculate view angle kicks
278 //
279         ent = &cl_entities[cl.viewentity];
280
281         VectorSubtract (from, ent->render.origin, from);
282         VectorNormalize (from);
283
284         AngleVectors (ent->render.angles, forward, right, NULL);
285
286         side = DotProduct (from, right);
287         v_dmg_roll = count*side*v_kickroll.value;
288
289         side = DotProduct (from, forward);
290         v_dmg_pitch = count*side*v_kickpitch.value;
291
292         v_dmg_time = v_kicktime.value;
293 }
294
295 static cshift_t v_cshift;
296
297 /*
298 ==================
299 V_cshift_f
300 ==================
301 */
302 static void V_cshift_f (void)
303 {
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));
308 }
309
310
311 /*
312 ==================
313 V_BonusFlash_f
314
315 When you run over an item, the server sends this command
316 ==================
317 */
318 static void V_BonusFlash_f (void)
319 {
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;
324 }
325
326 /*
327 =============
328 V_UpdateBlends
329 =============
330 */
331 void V_UpdateBlends (void)
332 {
333         float   r, g, b, a, a2;
334         int             j;
335
336         if (cl.worldmodel == NULL)
337         {
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;
346                 return;
347         }
348
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;
353
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;
358
359         // set contents color
360         switch (Mod_PointInLeaf (r_refdef.vieworg, cl.worldmodel)->contents)
361         {
362         case CONTENTS_EMPTY:
363         case CONTENTS_SOLID:
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;
368                 break;
369         case CONTENTS_LAVA:
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;
374                 break;
375         case CONTENTS_SLIME:
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;
380                 break;
381         default:
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;
386         }
387
388         if (cl.items & IT_QUAD)
389         {
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;
394         }
395         else if (cl.items & IT_SUIT)
396         {
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;
401         }
402         else if (cl.items & IT_INVISIBILITY)
403         {
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;
408         }
409         else if (cl.items & IT_INVULNERABILITY)
410         {
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;
415         }
416         else
417                 cl.cshifts[CSHIFT_POWERUP].percent = 0;
418
419         // LordHavoc: fixed V_CalcBlend
420         r = 0;
421         g = 0;
422         b = 0;
423         a = 0;
424
425         for (j=0 ; j<NUM_CSHIFTS ; j++)
426         {
427                 a2 = cl.cshifts[j].percent * (1.0f / 255.0f);
428
429                 if (a2 < 0)
430                         continue;
431                 if (a2 > 1)
432                         a2 = 1;
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
437         }
438         // saturate color (to avoid blending in black)
439         if (a)
440         {
441                 a2 = 1 / a;
442                 r *= a2;
443                 g *= a2;
444                 b *= a2;
445         }
446
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);
451 }
452
453 /*
454 ==============================================================================
455
456                                                 VIEW RENDERING
457
458 ==============================================================================
459 */
460
461 /*
462 ==============
463 V_AddIdle
464
465 Idle swaying
466 ==============
467 */
468 static void V_AddIdle (float idle)
469 {
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;
473 }
474
475
476 /*
477 ==================
478 V_CalcRefdef
479
480 ==================
481 */
482 void V_CalcRefdef (void)
483 {
484         entity_t        *ent, *view;
485         vec3_t          forward;
486         vec3_t          angles;
487         float           bob;
488         float           side;
489
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)
493         view = &cl.viewent;
494
495         V_DriftPitch ();
496
497         VectorCopy (ent->render.origin, r_refdef.vieworg);
498         if (!intimerefresh)
499                 VectorCopy (cl.viewangles, r_refdef.viewangles);
500
501         if (cl.intermission)
502         {
503                 view->render.model = NULL;
504                 V_AddIdle (1);
505         }
506         else if (chase_active.value)
507         {
508                 Chase_Update ();
509                 V_AddIdle (v_idlescale.value);
510         }
511         else
512         {
513                 side = V_CalcRoll (cl_entities[cl.viewentity].render.angles, cl.velocity);
514                 r_refdef.viewangles[ROLL] += side;
515
516                 if (v_dmg_time > 0)
517                 {
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;
521                 }
522
523                 if (cl.stats[STAT_HEALTH] <= 0)
524                         r_refdef.viewangles[ROLL] = 80; // dead view angle
525
526                 V_AddIdle (v_idlescale.value);
527
528                 // offsets
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];
532
533                 AngleVectors (angles, forward, NULL, NULL);
534
535                 bob = V_CalcBob ();
536
537                 r_refdef.vieworg[2] += cl.viewheight + bob;
538
539                 // set up gun
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?)
549                 CL_LerpUpdate(view);
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;
554
555                 // LordHavoc: origin view kick added
556                 if (!intimerefresh)
557                 {
558                         VectorAdd(r_refdef.viewangles, cl.punchangle, r_refdef.viewangles);
559                         VectorAdd(r_refdef.vieworg, cl.punchvector, r_refdef.vieworg);
560                 }
561
562                 // copy to refdef
563                 r_refdef.viewent = view->render;
564         }
565 }
566
567 //============================================================================
568
569 /*
570 =============
571 V_Init
572 =============
573 */
574 void V_Init (void)
575 {
576         Cmd_AddCommand ("v_cshift", V_cshift_f);
577         Cmd_AddCommand ("bf", V_BonusFlash_f);
578         Cmd_AddCommand ("centerview", V_StartPitchDrift);
579
580         Cvar_RegisterVariable (&v_centermove);
581         Cvar_RegisterVariable (&v_centerspeed);
582
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);
589
590         Cvar_RegisterVariable (&v_idlescale);
591         Cvar_RegisterVariable (&crosshair);
592
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);
598
599         Cvar_RegisterVariable (&v_kicktime);
600         Cvar_RegisterVariable (&v_kickroll);
601         Cvar_RegisterVariable (&v_kickpitch);
602 }
603
604