fcdac111e401aedbbd01c54a14a82bf294179385
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / weapons / projectile.qc
1 #include "projectile.qh"
2
3 #include "../autocvars.qh"
4 #include "../defs.qh"
5 #include "../main.qh"
6 #include <client/mutators/_mod.qh>
7
8 #include <common/constants.qh>
9 #include <common/effects/effect.qh>
10 #include <common/effects/all.qh>
11 #include <common/net_linked.qh>
12 #include <common/physics/movetypes/movetypes.qh>
13
14 #include <common/mutators/mutator/nades/nades.qh>
15
16 #include <lib/csqcmodel/interpolate.qh>
17
18 #include <lib/warpzone/anglestransform.qh>
19
20 .float alpha;
21 .float scale;
22 .vector colormod;
23
24 void SUB_Stop(entity this, entity toucher)
25 {
26         this.velocity = this.avelocity = '0 0 0';
27         set_movetype(this, MOVETYPE_NONE);
28 }
29
30 void Projectile_ResetTrail(entity this, vector to)
31 {
32         this.trail_oldorigin = to;
33         this.trail_oldtime = time;
34 }
35
36 void Projectile_DrawTrail(entity this, vector to)
37 {
38         vector from = this.trail_oldorigin;
39         // float t0 = this.trail_oldtime;
40         this.trail_oldorigin = to;
41         this.trail_oldtime = time;
42
43         // force the effect even for stationary firemine
44         if (this.cnt == PROJECTILE_FIREMINE)
45                 if (from == to)
46                         from.z += 1;
47
48         if (this.traileffect)
49         {
50                 particles_alphamin = particles_alphamax = particles_fade = sqrt(this.alpha);
51                 entity eff = REGISTRY_GET(Effects, this.traileffect);
52                 boxparticles(particleeffectnum(eff), this, from, to, this.velocity, this.velocity, 1, PARTICLES_USEALPHA | PARTICLES_USEFADE | PARTICLES_DRAWASTRAIL);
53         }
54 }
55
56 void Projectile_Draw(entity this)
57 {
58         vector rot;
59         vector trailorigin;
60         int f;
61         bool drawn;
62         float t;
63         float a;
64
65         f = this.flags;
66
67         if (this.count & 0x80)
68         {
69                 // UNSET_ONGROUND(this);
70                 if (this.move_movetype == MOVETYPE_NONE || this.move_movetype == MOVETYPE_FLY)
71                         Movetype_Physics_NoMatchServer(this);
72                 // the trivial movetypes do not have to match the
73                 // server's ticrate as they are ticrate independent
74                 // NOTE: this assumption is only true if MOVETYPE_FLY
75                 // projectiles detonate on impact. If they continue
76                 // moving, we might still be ticrate dependent.
77                 else
78                         Movetype_Physics_MatchServer(this, autocvar_cl_projectiles_sloppy);
79                 if (!IS_ONGROUND(this))
80                         if (this.velocity != '0 0 0')
81                                 this.angles = vectoangles(this.velocity);
82         }
83         else
84         {
85                 InterpolateOrigin_Do(this);
86         }
87
88         if (this.count & 0x80)
89         {
90                 drawn = (time >= this.spawntime - 0.02);
91                 t = max(time, this.spawntime);
92         }
93         else
94         {
95                 drawn = (this.iflags & IFLAG_VALID);
96                 t = time;
97         }
98
99         if (!(f & FL_ONGROUND))
100         {
101                 rot = '0 0 0';
102                 switch (this.cnt)
103                 {
104                         /*
105                         case PROJECTILE_GRENADE:
106                             rot = '-2000 0 0'; // forward
107                             break;
108                         */
109                         case PROJECTILE_GRENADE_BOUNCING:
110                                 rot = '0 -1000 0'; // sideways
111                                 break;
112                         case PROJECTILE_HOOKBOMB:
113                                 rot = '1000 0 0';  // forward
114                                 break;
115                         case PROJECTILE_ROCKET:
116                                 rot = '0 0 720'; // spinning
117                                 break;
118                         default:
119                                 break;
120                 }
121
122                 if (Projectile_isnade(this.cnt))
123                         rot = this.avelocity;
124
125                 this.angles = AnglesTransform_ToAngles(AnglesTransform_Multiply(AnglesTransform_FromAngles(this.angles), rot * (t - this.spawntime)));
126         }
127
128         vector ang;
129         ang = this.angles;
130         ang.x = -ang.x;
131         makevectors(ang);
132
133         a = 1 - (time - this.fade_time) * this.fade_rate;
134         this.alpha = bound(0, this.alphamod * a, 1);
135         if (this.alpha <= 0)
136                 drawn = 0;
137         this.renderflags = 0;
138
139         trailorigin = this.origin;
140         switch (this.cnt)
141         {
142                 case PROJECTILE_GRENADE:
143                 case PROJECTILE_GRENADE_BOUNCING:
144                         trailorigin += v_right * 1 + v_forward * -10;
145                         break;
146                 default:
147                         break;
148         }
149
150         if (Projectile_isnade(this.cnt))
151                 trailorigin += v_up * 4;
152
153         if (drawn)
154                 Projectile_DrawTrail(this, trailorigin);
155         else
156                 Projectile_ResetTrail(this, trailorigin);
157
158         this.drawmask = 0;
159
160         if (!drawn)
161                 return;
162
163         switch (this.cnt)
164         {
165                 // Possibly add dlights here.
166                 default:
167                         break;
168         }
169
170         this.drawmask = MASK_NORMAL;
171 }
172
173 void loopsound(entity e, int ch, Sound samp, float vol, float attn)
174 {
175         TC(int, ch);
176         if (e.silent)
177                 return;
178
179         sound(e, ch, samp, vol, attn);
180         e.snd_looping = ch;
181 }
182
183 void Ent_RemoveProjectile(entity this)
184 {
185         if (this.count & 0x80)
186         {
187                 tracebox(this.origin, this.mins, this.maxs, this.origin + this.velocity * 0.05, MOVE_NORMAL, this);
188                 Projectile_DrawTrail(this, trace_endpos);
189         }
190 }
191
192 NET_HANDLE(ENT_CLIENT_PROJECTILE, bool isnew)
193 {
194         // projectile properties:
195         //   kind (interpolated, or clientside)
196         //
197         //   modelindex
198         //   origin
199         //   scale
200         //   if clientside:
201         //     velocity
202         //     gravity
203         //   soundindex (hardcoded list)
204         //   effects
205         //
206         // projectiles don't send angles, because they always follow the velocity
207
208         int f = ReadByte();
209         this.count = (f & 0x80);
210         this.flags |= FL_PROJECTILE;
211         this.iflags = (this.iflags & IFLAG_INTERNALMASK) | IFLAG_AUTOANGLES | IFLAG_ANGLES | IFLAG_ORIGIN;
212         this.solid = SOLID_TRIGGER;
213         // this.effects = EF_NOMODELFLAGS;
214
215         // this should make collisions with bmodels more exact, but it leads to
216         // projectiles no longer being able to lie on a bmodel
217         this.move_nomonsters = MOVE_WORLDONLY;
218         if (f & 0x40)
219                 SET_ONGROUND(this);
220         else
221                 UNSET_ONGROUND(this);
222
223         if (!this.move_time)
224         {
225                 // for some unknown reason, we don't need to care for
226                 // sv_gameplayfix_delayprojectiles here.
227                 this.move_time = time;
228                 this.spawntime = time;
229         }
230         else
231         {
232                 this.move_time = max(this.move_time, time);
233         }
234
235         if (!(this.count & 0x80))
236                 InterpolateOrigin_Undo(this);
237
238         if (f & 1)
239         {
240                 this.origin = ReadVector();
241                 setorigin(this, this.origin);
242                 if (this.count & 0x80)
243                 {
244                         this.velocity = ReadVector();
245                         if (f & 0x10)
246                                 this.gravity = ReadCoord();
247                         else
248                                 this.gravity = 0;  // none
249                 }
250
251                 if (time == this.spawntime || (this.count & 0x80) || (f & 0x08))
252                 {
253                         this.trail_oldorigin = this.origin;
254                         if (!(this.count & 0x80))
255                                 InterpolateOrigin_Reset(this);
256                 }
257
258                 if (f & 0x20)
259                 {
260                         this.fade_time = time + ReadByte() * ticrate;
261                         this.fade_rate = 1 / (ReadByte() * ticrate);
262                 }
263                 else
264                 {
265                         this.fade_time = 0;
266                         this.fade_rate = 0;
267                 }
268
269                 int proj_team = ReadByte();
270                 this.team = proj_team - 1;
271
272                 if(teamplay)
273                 {
274                         if(proj_team)
275                                 this.colormap = (this.team) * 0x11; // note: team - 1 on server (client uses different numbers)
276                         else
277                                 this.colormap = 0x00;
278                         this.colormap |= BIT(10); // RENDER_COLORMAPPED
279                 }
280                 else
281                         this.colormap = proj_team;
282                 // TODO: projectiles use glowmaps for their color, not teams
283                 #if 0
284                 if(this.colormap > 0)
285                         this.glowmod = colormapPaletteColor(this.colormap & 0x0F, true) * 2;
286                 else
287                         this.glowmod = '1 1 1';
288                 #endif
289         }
290
291         if (f & 2)
292         {
293                 this.cnt = ReadByte();
294
295                 this.silent = (this.cnt & 0x80);
296                 this.cnt = (this.cnt & 0x7F);
297
298                 this.scale = 1;
299                 this.traileffect = 0;
300                 switch (this.cnt)
301                 {
302 #define HANDLE(id) case PROJECTILE_##id: setmodel(this, MDL_PROJECTILE_##id);
303                         HANDLE(ELECTRO)            this.traileffect = EFFECT_TR_NEXUIZPLASMA.m_id; break;
304                         HANDLE(ROCKET)             this.traileffect = EFFECT_TR_ROCKET.m_id; this.scale = 2; break;
305                         HANDLE(CRYLINK)            this.traileffect = EFFECT_TR_CRYLINKPLASMA.m_id; break;
306                         HANDLE(CRYLINK_BOUNCING)   this.traileffect = EFFECT_TR_CRYLINKPLASMA.m_id; break;
307                         HANDLE(ELECTRO_BEAM)       this.traileffect = EFFECT_TR_NEXUIZPLASMA.m_id; break;
308                         HANDLE(GRENADE)            this.traileffect = EFFECT_TR_GRENADE.m_id; break;
309                         HANDLE(GRENADE_BOUNCING)   this.traileffect = EFFECT_TR_GRENADE.m_id; break;
310                         HANDLE(MINE)               this.traileffect = EFFECT_TR_GRENADE.m_id; break;
311                         HANDLE(BLASTER)            this.traileffect = EFFECT_Null.m_id; break;
312                         HANDLE(ARC_BOLT)           this.traileffect = EFFECT_Null.m_id; break;
313                         HANDLE(HLAC)               this.traileffect = EFFECT_Null.m_id; break;
314                         HANDLE(PORTO_RED)          this.traileffect = EFFECT_TR_WIZSPIKE.m_id; this.scale = 4; break;
315                         HANDLE(PORTO_BLUE)         this.traileffect = EFFECT_TR_WIZSPIKE.m_id; this.scale = 4; break;
316                         HANDLE(HOOKBOMB)           this.traileffect = EFFECT_TR_KNIGHTSPIKE.m_id; break;
317                         HANDLE(HAGAR)              this.traileffect = EFFECT_HAGAR_ROCKET.m_id; this.scale = 0.75; break;
318                         HANDLE(HAGAR_BOUNCING)     this.traileffect = EFFECT_HAGAR_ROCKET.m_id; this.scale = 0.75; break;
319                         HANDLE(FIREBALL)           this.modelindex = 0; this.traileffect = EFFECT_FIREBALL.m_id; break; // particle effect is good enough
320                         HANDLE(FIREMINE)           this.modelindex = 0; this.traileffect = EFFECT_FIREMINE.m_id; break; // particle effect is good enough
321                         HANDLE(TAG)                this.traileffect = EFFECT_TR_ROCKET.m_id; break;
322                         HANDLE(FLAC)               this.scale = 0.4; this.traileffect = EFFECT_FLAC_TRAIL.m_id; break;
323                         HANDLE(SEEKER)             this.traileffect = EFFECT_SEEKER_TRAIL.m_id; break;
324
325                         HANDLE(MAGE_SPIKE)         this.traileffect = EFFECT_TR_VORESPIKE.m_id; break;
326                         HANDLE(SHAMBLER_LIGHTNING) this.traileffect = EFFECT_TR_NEXUIZPLASMA.m_id; break;
327
328                         HANDLE(RAPTORBOMB)         this.gravity = 1; this.avelocity = '0 0 180'; this.traileffect = EFFECT_Null.m_id; break;
329                         HANDLE(RAPTORBOMBLET)      this.gravity = 1; this.avelocity = '0 0 180'; this.traileffect = EFFECT_Null.m_id; break;
330                         HANDLE(RAPTORCANNON)       this.traileffect = EFFECT_TR_CRYLINKPLASMA.m_id; break;
331
332                         HANDLE(SPIDERROCKET)       this.traileffect = EFFECT_SPIDERBOT_ROCKET_TRAIL.m_id; break;
333                         HANDLE(WAKIROCKET)         this.traileffect = EFFECT_RACER_ROCKET_TRAIL.m_id; break;
334                         HANDLE(WAKICANNON)         this.traileffect = EFFECT_Null.m_id; break;
335
336                         HANDLE(BUMBLE_GUN)         this.traileffect = EFFECT_TR_NEXUIZPLASMA.m_id; break;
337                         HANDLE(BUMBLE_BEAM)        this.traileffect = EFFECT_TR_NEXUIZPLASMA.m_id; break;
338
339                         HANDLE(RPC)                this.traileffect = EFFECT_TR_ROCKET.m_id; break;
340
341                         HANDLE(ROCKETMINSTA_LASER) this.traileffect = EFFECT_ROCKETMINSTA_LASER(this.team).m_id; break;
342 #undef HANDLE
343                         default:
344                                 if (MUTATOR_CALLHOOK(Ent_Projectile, this))
345                                         break;
346
347                                 error("Received invalid CSQC projectile, can't work with this!");
348                                 break;
349                 }
350
351                 this.mins = '0 0 0';
352                 this.maxs = '0 0 0';
353                 this.colormod = '0 0 0';
354                 settouch(this, SUB_Stop);
355                 set_movetype(this, MOVETYPE_TOSS);
356                 this.alphamod = 1;
357
358                 switch (this.cnt)
359                 {
360                         case PROJECTILE_ELECTRO:
361                                 // only new engines support sound moving with object
362                                 loopsound(this, CH_SHOTS_SINGLE, SND_ELECTRO_FLY, VOL_BASE, ATTEN_NORM);
363                                 this.mins = '-4 -4 -4';
364                                 this.maxs = '4 4 4';
365                                 set_movetype(this, MOVETYPE_BOUNCE);
366                                 settouch(this, func_null);
367                                 this.bouncefactor = WEP_CVAR_SEC(electro, bouncefactor);
368                                 this.bouncestop = WEP_CVAR_SEC(electro, bouncestop);
369                                 break;
370                         case PROJECTILE_RPC:
371                         case PROJECTILE_ROCKET:
372                                 loopsound(this, CH_SHOTS_SINGLE, SND_ROCKET_FLY, VOL_BASE, ATTEN_NORM);
373                                 this.mins = '-3 -3 -3';
374                                 this.maxs = '3 3 3';
375                                 break;
376                         case PROJECTILE_GRENADE:
377                                 this.mins = '-3 -3 -3';
378                                 this.maxs = '3 3 3';
379                                 break;
380                         case PROJECTILE_GRENADE_BOUNCING:
381                                 this.mins = '-3 -3 -3';
382                                 this.maxs = '3 3 3';
383                                 set_movetype(this, MOVETYPE_BOUNCE);
384                                 settouch(this, func_null);
385                                 this.bouncefactor = WEP_CVAR(mortar, bouncefactor);
386                                 this.bouncestop = WEP_CVAR(mortar, bouncestop);
387                                 break;
388                         case PROJECTILE_SHAMBLER_LIGHTNING:
389                                 this.mins = '-8 -8 -8';
390                                 this.maxs = '8 8 8';
391                                 this.scale = 2.5;
392                                 this.avelocity = randomvec() * 720;
393                                 break;
394                         case PROJECTILE_MINE:
395                                 this.mins = '-4 -4 -4';
396                                 this.maxs = '4 4 4';
397                                 break;
398                         case PROJECTILE_PORTO_RED:
399                                 this.colormod = '2 1 1';
400                                 this.alphamod = 0.5;
401                                 set_movetype(this, MOVETYPE_BOUNCE);
402                                 settouch(this, func_null);
403                                 break;
404                         case PROJECTILE_PORTO_BLUE:
405                                 this.colormod = '1 1 2';
406                                 this.alphamod = 0.5;
407                                 set_movetype(this, MOVETYPE_BOUNCE);
408                                 settouch(this, func_null);
409                                 break;
410                         case PROJECTILE_HAGAR_BOUNCING:
411                                 set_movetype(this, MOVETYPE_BOUNCE);
412                                 settouch(this, func_null);
413                                 break;
414                         case PROJECTILE_CRYLINK_BOUNCING:
415                                 set_movetype(this, MOVETYPE_BOUNCE);
416                                 settouch(this, func_null);
417                                 break;
418                         case PROJECTILE_FIREBALL:
419                                 loopsound(this, CH_SHOTS_SINGLE, SND_FIREBALL_FLY2, VOL_BASE, ATTEN_NORM);
420                                 this.mins = '-16 -16 -16';
421                                 this.maxs = '16 16 16';
422                                 break;
423                         case PROJECTILE_FIREMINE:
424                                 loopsound(this, CH_SHOTS_SINGLE, SND_FIREBALL_FLY, VOL_BASE, ATTEN_NORM);
425                                 set_movetype(this, MOVETYPE_BOUNCE);
426                                 settouch(this, func_null);
427                                 this.mins = '-4 -4 -4';
428                                 this.maxs = '4 4 4';
429                                 break;
430                         case PROJECTILE_TAG:
431                                 this.mins = '-2 -2 -2';
432                                 this.maxs = '2 2 2';
433                                 break;
434                         case PROJECTILE_FLAC:
435                                 this.mins = '-2 -2 -2';
436                                 this.maxs = '2 2 2';
437                                 break;
438                         case PROJECTILE_SEEKER:
439                                 loopsound(this, CH_SHOTS_SINGLE, SND_TAG_ROCKET_FLY, VOL_BASE, ATTEN_NORM);
440                                 this.mins = '-4 -4 -4';
441                                 this.maxs = '4 4 4';
442                                 break;
443                         case PROJECTILE_RAPTORBOMB:
444                                 this.mins = '-3 -3 -3';
445                                 this.maxs = '3 3 3';
446                                 break;
447                         case PROJECTILE_RAPTORBOMBLET:
448                                 break;
449                         case PROJECTILE_RAPTORCANNON:
450                                 break;
451                         case PROJECTILE_SPIDERROCKET:
452                                 loopsound(this, CH_SHOTS_SINGLE, SND_TAG_ROCKET_FLY, VOL_BASE, ATTEN_NORM);
453                                 break;
454                         case PROJECTILE_WAKIROCKET:
455                                 loopsound(this, CH_SHOTS_SINGLE, SND_TAG_ROCKET_FLY, VOL_BASE, ATTEN_NORM);
456                                 break;
457                         /*
458                         case PROJECTILE_WAKICANNON:
459                             break;
460                         case PROJECTILE_BUMBLE_GUN:
461                             // only new engines support sound moving with object
462                             loopsound(this, CH_SHOTS_SINGLE, SND_ELECTRO_FLY, VOL_BASE, ATTEN_NORM);
463                             this.mins = '0 0 -4';
464                             this.maxs = '0 0 -4';
465                             this.move_movetype = MOVETYPE_BOUNCE;
466                             settouch(this, func_null);
467                             this.bouncefactor = WEP_CVAR_SEC(electro, bouncefactor);
468                             this.bouncestop = WEP_CVAR_SEC(electro, bouncestop);
469                             break;
470                         */
471                         default:
472                                 break;
473                 }
474
475                 MUTATOR_CALLHOOK(EditProjectile, this);
476
477                 setsize(this, this.mins, this.maxs);
478         }
479
480         return = true;
481
482         if (this.gravity)
483         {
484                 if (this.move_movetype == MOVETYPE_FLY)
485                         set_movetype(this, MOVETYPE_TOSS);
486                 if (this.move_movetype == MOVETYPE_BOUNCEMISSILE)
487                         set_movetype(this, MOVETYPE_BOUNCE);
488         }
489         else
490         {
491                 if (this.move_movetype == MOVETYPE_TOSS)
492                         set_movetype(this, MOVETYPE_FLY);
493                 if (this.move_movetype == MOVETYPE_BOUNCE)
494                         set_movetype(this, MOVETYPE_BOUNCEMISSILE);
495         }
496
497         if (!(this.count & 0x80))
498                 InterpolateOrigin_Note(this);
499
500         this.classname = "csqcprojectile";
501         this.draw = Projectile_Draw;
502         if (isnew) IL_PUSH(g_drawables, this);
503         this.entremove = Ent_RemoveProjectile;
504 }
505
506 PRECACHE(Projectiles)
507 {
508         MUTATOR_CALLHOOK(PrecacheProjectiles);
509 }