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