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