1#include "arc.qh" 2#ifndef IMPLEMENTATION 3CLASS(Arc, Weapon) 4/* ammotype */ ATTRIB(Arc, ammo_field, .int, ammo_cells); 5/* impulse */ ATTRIB(Arc, impulse, int, 3); 6/* flags */ ATTRIB(Arc, spawnflags, int, WEP_FLAG_NORMAL); 7/* rating */ ATTRIB(Arc, bot_pickupbasevalue, float, 8000); 8/* color */ ATTRIB(Arc, wpcolor, vector, '1 1 1'); 9/* modelname */ ATTRIB(Arc, mdl, string, "arc"); 10#ifdef GAMEQC 11/* model */ ATTRIB(Arc, m_model, Model, MDL_ARC_ITEM); 12#endif 13/* crosshair */ ATTRIB(Arc, w_crosshair, string, "gfx/crosshairhlac"); 14/* crosshair */ ATTRIB(Arc, w_crosshair_size, float, 0.7); 15/* wepimg */ ATTRIB(Arc, model2, string, "weaponarc"); 16/* refname */ ATTRIB(Arc, netname, string, "arc"); 17/* wepname */ ATTRIB(Arc, m_name, string, _("Arc")); 18 19#define X(BEGIN, P, END, class, prefix) \ 20 BEGIN(class) \ 21 P(class, prefix, bolt, float, NONE) \ 22 P(class, prefix, bolt_ammo, float, NONE) \ 23 P(class, prefix, bolt_damageforcescale, float, NONE) \ 24 P(class, prefix, bolt_damage, float, NONE) \ 25 P(class, prefix, bolt_edgedamage, float, NONE) \ 26 P(class, prefix, bolt_force, float, NONE) \ 27 P(class, prefix, bolt_health, float, NONE) \ 28 P(class, prefix, bolt_lifetime, float, NONE) \ 29 P(class, prefix, bolt_radius, float, NONE) \ 30 P(class, prefix, bolt_refire, float, NONE) \ 31 P(class, prefix, bolt_speed, float, NONE) \ 32 P(class, prefix, bolt_spread, float, NONE) \ 33 P(class, prefix, beam_ammo, float, NONE) \ 34 P(class, prefix, beam_animtime, float, NONE) \ 35 P(class, prefix, beam_botaimlifetime, float, NONE) \ 36 P(class, prefix, beam_botaimspeed, float, NONE) \ 37 P(class, prefix, beam_damage, float, NONE) \ 38 P(class, prefix, beam_degreespersegment, float, NONE) \ 39 P(class, prefix, beam_distancepersegment, float, NONE) \ 40 P(class, prefix, beam_falloff_halflifedist, float, NONE) \ 41 P(class, prefix, beam_falloff_maxdist, float, NONE) \ 42 P(class, prefix, beam_falloff_mindist, float, NONE) \ 43 P(class, prefix, beam_force, float, NONE) \ 44 P(class, prefix, beam_healing_amax, float, NONE) \ 45 P(class, prefix, beam_healing_aps, float, NONE) \ 46 P(class, prefix, beam_healing_hmax, float, NONE) \ 47 P(class, prefix, beam_healing_hps, float, NONE) \ 48 P(class, prefix, beam_heat, float, NONE) /* heat increase per second (primary) */ \ 49 P(class, prefix, beam_maxangle, float, NONE) \ 50 P(class, prefix, beam_nonplayerdamage, float, NONE) \ 51 P(class, prefix, beam_range, float, NONE) \ 52 P(class, prefix, beam_refire, float, NONE) \ 53 P(class, prefix, beam_returnspeed, float, NONE) \ 54 P(class, prefix, beam_tightness, float, NONE) \ 55 P(class, prefix, burst_ammo, float, NONE) \ 56 P(class, prefix, burst_damage, float, NONE) \ 57 P(class, prefix, burst_healing_aps, float, NONE) \ 58 P(class, prefix, burst_healing_hps, float, NONE) \ 59 P(class, prefix, burst_heat, float, NONE) /* heat increase per second (secondary) */ \ 60 P(class, prefix, cooldown, float, NONE) /* heat decrease per second when resting */ \ 61 P(class, prefix, cooldown_release, float, NONE) /* delay weapon re-use when releasing button */ \ 62 P(class, prefix, overheat_max, float, NONE) /* maximum heat before jamming */ \ 63 P(class, prefix, overheat_min, float, NONE) /* minimum heat to wait for cooldown */ \ 64 P(class, prefix, switchdelay_drop, float, NONE) \ 65 P(class, prefix, switchdelay_raise, float, NONE) \ 66 P(class, prefix, weaponreplace, string, NONE) \ 67 P(class, prefix, weaponstartoverride, float, NONE) \ 68 P(class, prefix, weaponstart, float, NONE) \ 69 P(class, prefix, weaponthrowable, float, NONE) \ 70 END() 71 W_PROPS(X, Arc, arc) 72#undef X 73 74ENDCLASS(Arc) 75REGISTER_WEAPON(ARC, arc, NEW(Arc)); 76 77 78#ifdef GAMEQC 79const float ARC_MAX_SEGMENTS = 20; 80vector arc_shotorigin[4]; 81.vector beam_start; 82.vector beam_dir; 83.vector beam_wantdir; 84.int beam_type; 85 86const int ARC_BT_MISS = 0x00; 87const int ARC_BT_WALL = 0x01; 88const int ARC_BT_HEAL = 0x02; 89const int ARC_BT_HIT = 0x03; 90const int ARC_BT_BURST_MISS = 0x10; 91const int ARC_BT_BURST_WALL = 0x11; 92const int ARC_BT_BURST_HEAL = 0x12; 93const int ARC_BT_BURST_HIT = 0x13; 94const int ARC_BT_BURSTMASK = 0x10; 95 96const int ARC_SF_SETTINGS = BIT(0); 97const int ARC_SF_START = BIT(1); 98const int ARC_SF_WANTDIR = BIT(2); 99const int ARC_SF_BEAMDIR = BIT(3); 100const int ARC_SF_BEAMTYPE = BIT(4); 101const int ARC_SF_LOCALMASK = ARC_SF_START | ARC_SF_WANTDIR | ARC_SF_BEAMDIR; 102#endif 103#ifdef SVQC 104.entity arc_beam; 105.bool arc_BUTTON_ATCK_prev; // for better animation control 106.float beam_prev; 107.float beam_initialized; 108.float beam_bursting; 109.float beam_teleporttime; 110.float beam_heat; // (beam) amount of heat produced 111.float arc_overheat; // (dropped arc/player) time during which it's too hot 112.float arc_cooldown; // (dropped arc/player) cooling speed 113.float arc_heat_percent = _STAT(ARC_HEAT); 114.float arc_smoke_sound; 115#endif 116#ifdef CSQC 117 118.vector beam_color; 119.float beam_alpha; 120.float beam_thickness; 121.entity beam_traileffect; 122.entity beam_hiteffect; 123.float beam_hitlight[4]; // 0: radius, 123: rgb 124.entity beam_muzzleeffect; 125.float beam_muzzlelight[4]; // 0: radius, 123: rgb 126.string beam_image; 127 128.entity beam_muzzleentity; 129 130.float beam_degreespersegment; 131.float beam_distancepersegment; 132.float beam_usevieworigin; 133.float beam_initialized; 134.float beam_maxangle; 135.float beam_range; 136.float beam_returnspeed; 137.float beam_tightness; 138.vector beam_shotorigin; 139 140entity Draw_ArcBeam_callback_entity; 141float Draw_ArcBeam_callback_last_thickness; 142vector Draw_ArcBeam_callback_last_top; // NOTE: in same coordinate system as player. 143vector Draw_ArcBeam_callback_last_bottom; // NOTE: in same coordinate system as player. 144#endif 145#endif 146#ifdef IMPLEMENTATION 147#ifdef SVQC 148spawnfunc(weapon_arc) { weapon_defaultspawnfunc(this, WEP_ARC); } 149 150bool W_Arc_Beam_Send(entity this, entity to, int sf) 151{ 152 WriteHeader(MSG_ENTITY, ENT_CLIENT_ARC_BEAM); 153 154 // Truncate information when this beam is displayed to the owner client 155 // - The owner client has no use for beam start position or directions, 156 // it always figures this information out for itself with csqc code. 157 // - Spectating the owner also truncates this information. 158 float drawlocal = ((to == this.owner) || ((to.enemy == this.owner) && IS_SPEC(to))); 159 if(drawlocal) { sf &= ~ARC_SF_LOCALMASK; } 160 161 WriteByte(MSG_ENTITY, sf); 162 WriteByte(MSG_ENTITY, weaponslot(this.weaponentity_fld)); 163 164 if(sf & ARC_SF_SETTINGS) // settings information 165 { 166 WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_degreespersegment)); 167 WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_distancepersegment)); 168 WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_maxangle)); 169 WriteCoord(MSG_ENTITY, WEP_CVAR(arc, beam_range)); 170 WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_returnspeed)); 171 WriteByte(MSG_ENTITY, WEP_CVAR(arc, beam_tightness) * 10); 172 173 WriteByte(MSG_ENTITY, drawlocal); 174 WriteByte(MSG_ENTITY, etof(this.owner)); 175 } 176 if(sf & ARC_SF_START) // starting location 177 { 178 WriteCoord(MSG_ENTITY, this.beam_start.x); 179 WriteCoord(MSG_ENTITY, this.beam_start.y); 180 WriteCoord(MSG_ENTITY, this.beam_start.z); 181 } 182 if(sf & ARC_SF_WANTDIR) // want/aim direction 183 { 184 WriteCoord(MSG_ENTITY, this.beam_wantdir.x); 185 WriteCoord(MSG_ENTITY, this.beam_wantdir.y); 186 WriteCoord(MSG_ENTITY, this.beam_wantdir.z); 187 } 188 if(sf & ARC_SF_BEAMDIR) // beam direction 189 { 190 WriteCoord(MSG_ENTITY, this.beam_dir.x); 191 WriteCoord(MSG_ENTITY, this.beam_dir.y); 192 WriteCoord(MSG_ENTITY, this.beam_dir.z); 193 } 194 if(sf & ARC_SF_BEAMTYPE) // beam type 195 { 196 WriteByte(MSG_ENTITY, this.beam_type); 197 } 198 199 return true; 200} 201 202void Reset_ArcBeam(entity player, vector forward) 203{ 204 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) 205 { 206 .entity weaponentity = weaponentities[slot]; 207 if(!player.(weaponentity).arc_beam) 208 continue; 209 player.(weaponentity).arc_beam.beam_dir = forward; 210 player.(weaponentity).arc_beam.beam_teleporttime = time; 211 } 212} 213 214float Arc_GetHeat_Percent(entity player, .entity weaponentity) 215{ 216 if ( WEP_CVAR(arc, overheat_max) <= 0 || WEP_CVAR(arc, overheat_max) <= 0 ) 217 { 218 player.arc_overheat = 0; 219 return 0; 220 } 221 222 if ( player.(weaponentity).arc_beam ) 223 return player.(weaponentity).arc_beam.beam_heat/WEP_CVAR(arc, overheat_max); 224 225 if ( player.arc_overheat > time ) 226 { 227 return (player.arc_overheat-time) / WEP_CVAR(arc, overheat_max) 228 * player.arc_cooldown; 229 } 230 231 return 0; 232} 233void Arc_Player_SetHeat(entity player, .entity weaponentity) 234{ 235 player.arc_heat_percent = Arc_GetHeat_Percent(player, weaponentity); 236 //dprint("Heat: ",ftos(player.arc_heat_percent*100),"%\n"); 237} 238 239void W_Arc_Bolt_Explode(entity this, entity directhitentity) 240{ 241 this.event_damage = func_null; 242 RadiusDamage(this, this.realowner, WEP_CVAR(arc, bolt_damage), WEP_CVAR(arc, bolt_edgedamage), WEP_CVAR(arc, bolt_radius), NULL, NULL, WEP_CVAR(arc, bolt_force), this.projectiledeathtype, directhitentity); 243 244 delete(this); 245} 246 247void W_Arc_Bolt_Explode_use(entity this, entity actor, entity trigger) 248{ 249 W_Arc_Bolt_Explode(this, trigger); 250} 251 252void W_Arc_Bolt_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) 253{ 254 if(this.health <= 0) 255 return; 256 257 if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) 258 return; // g_projectiles_damage says to halt 259 260 this.health = this.health - damage; 261 this.angles = vectoangles(this.velocity); 262 263 if(this.health <= 0) 264 W_PrepareExplosionByDamage(this, attacker, getthink(this)); 265} 266 267void W_Arc_Bolt_Touch(entity this, entity toucher) 268{ 269 PROJECTILE_TOUCH(this, toucher); 270 this.use(this, NULL, toucher); 271} 272 273void W_Arc_Attack_Bolt(Weapon thiswep, entity actor, .entity weaponentity) 274{ 275 entity missile; 276 277 W_DecreaseAmmo(thiswep, actor, WEP_CVAR(arc, bolt_ammo), weaponentity); 278 279 W_SetupShot(actor, weaponentity, false, 2, SND_LASERGUN_FIRE, CH_WEAPON_A, WEP_CVAR(arc, bolt_damage)); 280 281 Send_Effect(EFFECT_ARC_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); 282 283 missile = new(missile); 284 missile.owner = missile.realowner = actor; 285 missile.bot_dodge = true; 286 IL_PUSH(g_bot_dodge, missile); 287 missile.bot_dodgerating = WEP_CVAR(arc, bolt_damage); 288 289 missile.takedamage = DAMAGE_YES; 290 missile.health = WEP_CVAR(arc, bolt_health); 291 missile.damageforcescale = WEP_CVAR(arc, bolt_damageforcescale); 292 missile.event_damage = W_Arc_Bolt_Damage; 293 missile.damagedbycontents = true; 294 IL_PUSH(g_damagedbycontents, missile); 295 296 settouch(missile, W_Arc_Bolt_Touch); 297 missile.use = W_Arc_Bolt_Explode_use; 298 setthink(missile, adaptor_think2use_hittype_splash); 299 missile.nextthink = time + WEP_CVAR(arc, bolt_lifetime); 300 PROJECTILE_MAKETRIGGER(missile); 301 missile.projectiledeathtype = WEP_ARC.m_id | HITTYPE_SECONDARY; 302 setorigin(missile, w_shotorg); 303 setsize(missile, '0 0 0', '0 0 0'); 304 305 set_movetype(missile, MOVETYPE_FLY); 306 W_SetupProjVelocity_PRE(missile, arc, bolt_); 307 308 missile.angles = vectoangles(missile.velocity); 309 missile.flags = FL_PROJECTILE; 310 missile.missile_flags = MIF_SPLASH; 311 312 CSQCProjectile(missile, true, PROJECTILE_ARC_BOLT, true); 313 314 MUTATOR_CALLHOOK(EditProjectile, actor, missile); 315} 316 317void W_Arc_Beam_Think(entity this) 318{ 319 .entity weaponentity = this.weaponentity_fld; 320 entity own = this.owner; 321 if(this != own.(weaponentity).arc_beam) 322 { 323 delete(this); 324 return; 325 } 326 327 float burst = 0; 328 if( (PHYS_INPUT_BUTTON_ATCK2(own) && !WEP_CVAR(arc, bolt)) || this.beam_bursting) 329 { 330 if(!this.beam_bursting) 331 this.beam_bursting = true; 332 burst = ARC_BT_BURSTMASK; 333 } 334 335 Weapon thiswep = WEP_ARC; 336 337 if( 338 !IS_PLAYER(own) 339 || 340 (!thiswep.wr_checkammo1(thiswep, own, weaponentity) && !(own.items & IT_UNLIMITED_WEAPON_AMMO)) 341 || 342 IS_DEAD(own) 343 || 344 forbidWeaponUse(own) 345 || 346 own.(weaponentity).m_switchweapon != WEP_ARC 347 || 348 (!PHYS_INPUT_BUTTON_ATCK(own) && !burst ) 349 || 350 own.vehicle 351 || 352 (WEP_CVAR(arc, overheat_max) > 0 && this.beam_heat >= WEP_CVAR(arc, overheat_max)) 353 ) 354 { 355 if ( WEP_CVAR(arc, cooldown) > 0 ) 356 { 357 float cooldown_speed = 0; 358 if ( this.beam_heat > WEP_CVAR(arc, overheat_min) && WEP_CVAR(arc, cooldown) > 0 ) 359 { 360 cooldown_speed = WEP_CVAR(arc, cooldown); 361 } 362 else if ( !burst ) 363 { 364 cooldown_speed = this.beam_heat / WEP_CVAR(arc, beam_refire); 365 } 366 367 if ( cooldown_speed ) 368 { 369 if ( WEP_CVAR(arc, cooldown_release) || (WEP_CVAR(arc, overheat_max) > 0 && this.beam_heat >= WEP_CVAR(arc, overheat_max)) ) 370 own.arc_overheat = time + this.beam_heat / cooldown_speed; 371 own.arc_cooldown = cooldown_speed; 372 } 373 374 if ( WEP_CVAR(arc, overheat_max) > 0 && this.beam_heat >= WEP_CVAR(arc, overheat_max) ) 375 { 376 Send_Effect(EFFECT_ARC_OVERHEAT, 377 this.beam_start, this.beam_wantdir, 1 ); 378 sound(this, CH_WEAPON_A, SND_ARC_STOP, VOL_BASE, ATTN_NORM); 379 } 380 } 381 382 if(this == own.(weaponentity).arc_beam) { own.(weaponentity).arc_beam = NULL; } 383 if(!thiswep.wr_checkammo1(thiswep, own, weaponentity) && !(own.items & IT_UNLIMITED_WEAPON_AMMO)) 384 { 385 // note: this doesn't force the switch 386 W_SwitchToOtherWeapon(own, weaponentity); 387 own.(weaponentity).arc_BUTTON_ATCK_prev = false; // hax 388 } 389 delete(this); 390 return; 391 } 392 393 // decrease ammo 394 float coefficient = frametime; 395 if(!(own.items & IT_UNLIMITED_WEAPON_AMMO)) 396 { 397 float rootammo; 398 if(burst) 399 { rootammo = WEP_CVAR(arc, burst_ammo); } 400 else 401 { rootammo = WEP_CVAR(arc, beam_ammo); } 402 403 if(rootammo) 404 { 405 coefficient = min(coefficient, own.(thiswep.ammo_field) / rootammo); 406 own.(thiswep.ammo_field) = max(0, own.(thiswep.ammo_field) - (rootammo * frametime)); 407 } 408 } 409 float heat_speed = burst ? WEP_CVAR(arc, burst_heat) : WEP_CVAR(arc, beam_heat); 410 this.beam_heat = min( WEP_CVAR(arc, overheat_max), this.beam_heat + heat_speed*frametime ); 411 412 makevectors(own.v_angle); 413 414 W_SetupShot_Range( 415 own, 416 weaponentity, // TODO 417 true, 418 0, 419 SND_Null, 420 0, 421 WEP_CVAR(arc, beam_damage) * coefficient, 422 WEP_CVAR(arc, beam_range) 423 ); 424 425 // After teleport, "lock" the beam until the teleport is confirmed. 426 if (time < this.beam_teleporttime + ANTILAG_LATENCY(own)) { 427 w_shotdir = this.beam_dir; 428 } 429 430 // network information: shot origin and want/aim direction 431 if(this.beam_start != w_shotorg) 432 { 433 this.SendFlags |= ARC_SF_START; 434 this.beam_start = w_shotorg; 435 } 436 if(this.beam_wantdir != w_shotdir) 437 { 438 this.SendFlags |= ARC_SF_WANTDIR; 439 this.beam_wantdir = w_shotdir; 440 } 441 442 if(!this.beam_initialized) 443 { 444 this.beam_dir = w_shotdir; 445 this.beam_initialized = true; 446 } 447 448 // WEAPONTODO: Detect player velocity so that the beam curves when moving too 449 // idea: blend together this.beam_dir with the inverted direction the player is moving in 450 // might have to make some special accomodation so that it only uses view_right and view_up 451 452 // note that if we do this, it'll always be corrected to a maximum angle by beam_maxangle handling 453 454 float segments; 455 if(this.beam_dir != w_shotdir) 456 { 457 // calculate how much we're going to move the end of the beam to the want position 458 // WEAPONTODO (server and client): 459 // blendfactor never actually becomes 0 in this situation, which is a problem 460 // regarding precision... this means that this.beam_dir and w_shotdir approach 461 // eachother, however they never actually become the same value with this method. 462 // Perhaps we should do some form of rounding/snapping? 463 float angle = vlen(w_shotdir - this.beam_dir) * RAD2DEG; 464 if(angle && (angle > WEP_CVAR(arc, beam_maxangle))) 465 { 466 // if the angle is greater than maxangle, force the blendfactor to make this the maximum factor 467 float blendfactor = bound( 468 0, 469 (1 - (WEP_CVAR(arc, beam_returnspeed) * frametime)), 470 min(WEP_CVAR(arc, beam_maxangle) / angle, 1) 471 ); 472 this.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (this.beam_dir * blendfactor)); 473 } 474 else 475 { 476 // the radius is not too far yet, no worries :D 477 float blendfactor = bound( 478 0, 479 (1 - (WEP_CVAR(arc, beam_returnspeed) * frametime)), 480 1 481 ); 482 this.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (this.beam_dir * blendfactor)); 483 } 484 485 // network information: beam direction 486 this.SendFlags |= ARC_SF_BEAMDIR; 487 488 // calculate how many segments are needed 489 float max_allowed_segments; 490 491 if(WEP_CVAR(arc, beam_distancepersegment)) 492 { 493 max_allowed_segments = min( 494 ARC_MAX_SEGMENTS, 495 1 + (vlen(w_shotdir / WEP_CVAR(arc, beam_distancepersegment))) 496 ); 497 } 498 else { max_allowed_segments = ARC_MAX_SEGMENTS; } 499 500 if(WEP_CVAR(arc, beam_degreespersegment)) 501 { 502 segments = bound( 503 1, 504 ( 505 min( 506 angle, 507 WEP_CVAR(arc, beam_maxangle) 508 ) 509 / 510 WEP_CVAR(arc, beam_degreespersegment) 511 ), 512 max_allowed_segments 513 ); 514 } 515 else { segments = 1; } 516 } 517 else { segments = 1; } 518 519 vector beam_endpos = (w_shotorg + (this.beam_dir * WEP_CVAR(arc, beam_range))); 520 vector beam_controlpoint = w_shotorg + w_shotdir * (WEP_CVAR(arc, beam_range) * (1 - WEP_CVAR(arc, beam_tightness))); 521 522 float i; 523 float new_beam_type = 0; 524 vector last_origin = w_shotorg; 525 for(i = 1; i <= segments; ++i) 526 { 527 // WEAPONTODO (client): 528 // In order to do nice fading and pointing on the starting segment, we must always 529 // have that drawn as a separate triangle... However, that is difficult to do when 530 // keeping in mind the above problems and also optimizing the amount of segments 531 // drawn on screen at any given time. (Automatic beam quality scaling, essentially) 532 533 vector new_origin = bezier_quadratic_getpoint( 534 w_shotorg, 535 beam_controlpoint, 536 beam_endpos, 537 i / segments); 538 vector new_dir = normalize(new_origin - last_origin); 539 540 WarpZone_traceline_antilag( 541 own, 542 last_origin, 543 new_origin, 544 MOVE_NORMAL, 545 own, 546 ANTILAG_LATENCY(own) 547 ); 548 549 // Do all the transforms for warpzones right now, as we already 550 // "are" in the post-trace system (if we hit a player, that's 551 // always BEHIND the last passed wz). 552 last_origin = trace_endpos; 553 w_shotorg = WarpZone_TransformOrigin(WarpZone_trace_transform, w_shotorg); 554 beam_controlpoint = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_controlpoint); 555 beam_endpos = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_endpos); 556 new_dir = WarpZone_TransformVelocity(WarpZone_trace_transform, new_dir); 557 558 float is_player = ( 559 IS_PLAYER(trace_ent) 560 || 561 trace_ent.classname == "body" 562 || 563 IS_MONSTER(trace_ent) 564 ); 565 566 if(trace_ent && trace_ent.takedamage && (is_player || WEP_CVAR(arc, beam_nonplayerdamage))) 567 { 568 // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?) 569 // NO. trace_endpos should be just fine. If not, 570 // that's an engine bug that needs proper debugging. 571 vector hitorigin = trace_endpos; 572 573 float falloff = ExponentialFalloff( 574 WEP_CVAR(arc, beam_falloff_mindist), 575 WEP_CVAR(arc, beam_falloff_maxdist), 576 WEP_CVAR(arc, beam_falloff_halflifedist), 577 vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, hitorigin) - w_shotorg) 578 ); 579 580 if(is_player && SAME_TEAM(own, trace_ent)) 581 { 582 float roothealth, rootarmor; 583 if(burst) 584 { 585 roothealth = WEP_CVAR(arc, burst_healing_hps); 586 rootarmor = WEP_CVAR(arc, burst_healing_aps); 587 } 588 else 589 { 590 roothealth = WEP_CVAR(arc, beam_healing_hps); 591 rootarmor = WEP_CVAR(arc, beam_healing_aps); 592 } 593 594 if(trace_ent.health <= WEP_CVAR(arc, beam_healing_hmax) && roothealth) 595 { 596 trace_ent.health = min( 597 trace_ent.health + (roothealth * coefficient), 598 WEP_CVAR(arc, beam_healing_hmax) 599 ); 600 } 601 if(trace_ent.armorvalue <= WEP_CVAR(arc, beam_healing_amax) && rootarmor) 602 { 603 trace_ent.armorvalue = min( 604 trace_ent.armorvalue + (rootarmor * coefficient), 605 WEP_CVAR(arc, beam_healing_amax) 606 ); 607 } 608 609 // stop rot, set visual effect 610 if(roothealth || rootarmor) 611 { 612 trace_ent.pauserothealth_finished = max( 613 trace_ent.pauserothealth_finished, 614 time + autocvar_g_balance_pause_health_rot 615 ); 616 trace_ent.pauserotarmor_finished = max( 617 trace_ent.pauserotarmor_finished, 618 time + autocvar_g_balance_pause_armor_rot 619 ); 620 new_beam_type = ARC_BT_HEAL; 621 } 622 } 623 else 624 { 625 float rootdamage; 626 if(is_player) 627 { 628 if(burst) 629 { rootdamage = WEP_CVAR(arc, burst_damage); } 630 else 631 { rootdamage = WEP_CVAR(arc, beam_damage); } 632 } 633 else 634 { rootdamage = WEP_CVAR(arc, beam_nonplayerdamage); } 635 636 if(accuracy_isgooddamage(own, trace_ent)) 637 { 638 accuracy_add( 639 own, 640 WEP_ARC.m_id, 641 0, 642 rootdamage * coefficient * falloff 643 ); 644 } 645 646 Damage( 647 trace_ent, 648 own, 649 own, 650 rootdamage * coefficient * falloff, 651 WEP_ARC.m_id, 652 hitorigin, 653 WEP_CVAR(arc, beam_force) * new_dir * coefficient * falloff 654 ); 655 656 new_beam_type = ARC_BT_HIT; 657 } 658 break; 659 } 660 else if(trace_fraction != 1) 661 { 662 // we collided with geometry 663 new_beam_type = ARC_BT_WALL; 664 break; 665 } 666 } 667 668 // te_explosion(trace_endpos); 669 670 // if we're bursting, use burst visual effects 671 new_beam_type |= burst; 672 673 // network information: beam type 674 if(new_beam_type != this.beam_type) 675 { 676 this.SendFlags |= ARC_SF_BEAMTYPE; 677 this.beam_type = new_beam_type; 678 } 679 680 own.(weaponentity).beam_prev = time; 681 this.nextthink = time; 682} 683 684void W_Arc_Beam(float burst, entity actor, .entity weaponentity) 685{ 686 687 // only play fire sound if 1 sec has passed since player let go the fire button 688 if(time - actor.(weaponentity).beam_prev > 1) 689 sound(actor, CH_WEAPON_A, SND_ARC_FIRE, VOL_BASE, ATTN_NORM); 690 691 entity beam = actor.(weaponentity).arc_beam = new(W_Arc_Beam); 692 beam.weaponentity_fld = weaponentity; 693 beam.solid = SOLID_NOT; 694 setthink(beam, W_Arc_Beam_Think); 695 beam.owner = actor; 696 set_movetype(beam, MOVETYPE_NONE); 697 beam.bot_dodge = true; 698 IL_PUSH(g_bot_dodge, beam); 699 beam.bot_dodgerating = WEP_CVAR(arc, beam_damage); 700 beam.beam_bursting = burst; 701 Net_LinkEntity(beam, false, 0, W_Arc_Beam_Send); 702 703 getthink(beam)(beam); 704} 705void Arc_Smoke(entity actor, .entity weaponentity) 706{ 707 makevectors(actor.v_angle); 708 W_SetupShot_Range(actor,weaponentity,true,0,SND_Null,0,0,0); 709 710 vector smoke_origin = w_shotorg + actor.velocity*frametime; 711 if ( actor.arc_overheat > time ) 712 { 713 if ( random() < actor.arc_heat_percent ) 714 Send_Effect(EFFECT_ARC_SMOKE, smoke_origin, '0 0 0', 1 ); 715 if ( PHYS_INPUT_BUTTON_ATCK(actor) || PHYS_INPUT_BUTTON_ATCK2(actor) ) 716 { 717 Send_Effect(EFFECT_ARC_OVERHEAT_FIRE, smoke_origin, w_shotdir, 1 ); 718 if ( !actor.arc_smoke_sound ) 719 { 720 actor.arc_smoke_sound = 1; 721 sound(actor, CH_SHOTS_SINGLE, SND_ARC_LOOP_OVERHEAT, VOL_BASE, ATTN_NORM); 722 } 723 } 724 } 725 else if ( actor.(weaponentity).arc_beam && WEP_CVAR(arc, overheat_max) > 0 && 726 actor.(weaponentity).arc_beam.beam_heat > WEP_CVAR(arc, overheat_min) ) 727 { 728 if ( random() < (actor.(weaponentity).arc_beam.beam_heat-WEP_CVAR(arc, overheat_min)) / 729 ( WEP_CVAR(arc, overheat_max)-WEP_CVAR(arc, overheat_min) ) ) 730 Send_Effect(EFFECT_ARC_SMOKE, smoke_origin, '0 0 0', 1 ); 731 } 732 733 if ( actor.arc_smoke_sound && ( actor.arc_overheat <= time || 734 !( PHYS_INPUT_BUTTON_ATCK(actor) || PHYS_INPUT_BUTTON_ATCK2(actor) ) ) || actor.(weaponentity).m_switchweapon != WEP_ARC ) 735 { 736 actor.arc_smoke_sound = 0; 737 sound(actor, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, ATTEN_NORM); 738 } 739} 740 741METHOD(Arc, wr_aim, void(entity thiswep, entity actor, .entity weaponentity)) 742{ 743 if(WEP_CVAR(arc, beam_botaimspeed)) 744 { 745 PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim( 746 actor, 747 weaponentity, 748 WEP_CVAR(arc, beam_botaimspeed), 749 0, 750 WEP_CVAR(arc, beam_botaimlifetime), 751 false 752 ); 753 } 754 else 755 { 756 PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim( 757 actor, 758 weaponentity, 759 1000000, 760 0, 761 0.001, 762 false 763 ); 764 } 765} 766METHOD(Arc, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire)) 767{ 768 Arc_Player_SetHeat(actor, weaponentity); 769 Arc_Smoke(actor, weaponentity); 770 771 bool beam_fire2 = ((fire & 2) && !WEP_CVAR(arc, bolt)); 772 773 if (time >= actor.arc_overheat) 774 if ((fire & 1) || beam_fire2 || actor.(weaponentity).arc_beam.beam_bursting) 775 { 776 777 if(actor.(weaponentity).arc_BUTTON_ATCK_prev) 778 { 779 #if 0 780 if(actor.animstate_startframe == actor.anim_shoot.x && actor.animstate_numframes == actor.anim_shoot.y) 781 weapon_thinkf(actor, weaponentity, WFRAME_DONTCHANGE, autocvar_g_balance_arc_primary_animtime, w_ready); 782 else 783 #endif 784 weapon_thinkf(actor, weaponentity, WFRAME_DONTCHANGE, WEP_CVAR(arc, beam_animtime), w_ready); 785 } 786 787 if((!actor.(weaponentity).arc_beam) || wasfreed(actor.(weaponentity).arc_beam)) 788 { 789 if(weapon_prepareattack(thiswep, actor, weaponentity, boolean(beam_fire2), 0)) 790 { 791 W_Arc_Beam(boolean(beam_fire2), actor, weaponentity); 792 793 if(!actor.(weaponentity).arc_BUTTON_ATCK_prev) 794 { 795 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready); 796 actor.(weaponentity).arc_BUTTON_ATCK_prev = true; 797 } 798 } 799 } 800 801 return; 802 } 803 else if(fire & 2) 804 { 805 if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR(arc, bolt_refire))) 806 { 807 W_Arc_Attack_Bolt(thiswep, actor, weaponentity); 808 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(arc, bolt_refire), w_ready); 809 } 810 } 811 812 if(actor.(weaponentity).arc_BUTTON_ATCK_prev) 813 { 814 int slot = weaponslot(weaponentity); 815 sound(actor, CH_WEAPON_A, SND_ARC_STOP, VOL_BASE, ATTN_NORM); 816 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready); 817 ATTACK_FINISHED(actor, slot) = time + WEP_CVAR(arc, beam_refire) * W_WeaponRateFactor(actor); 818 } 819 actor.(weaponentity).arc_BUTTON_ATCK_prev = false; 820 821 #if 0 822 if(fire & 2) 823 if(weapon_prepareattack(thiswep, actor, weaponentity, true, autocvar_g_balance_arc_secondary_refire)) 824 { 825 W_Arc_Attack2(); 826 actor.arc_count = autocvar_g_balance_arc_secondary_count; 827 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, autocvar_g_balance_arc_secondary_animtime, w_arc_checkattack); 828 actor.arc_secondarytime = time + autocvar_g_balance_arc_secondary_refire2 * W_WeaponRateFactor(actor); 829 } 830 #endif 831} 832METHOD(Arc, wr_init, void(entity thiswep)) 833{ 834 if(!arc_shotorigin[0]) 835 { 836 arc_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC.m_id), false, false, 1); 837 arc_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC.m_id), false, false, 2); 838 arc_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC.m_id), false, false, 3); 839 arc_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC.m_id), false, false, 4); 840 } 841} 842METHOD(Arc, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity)) 843{ 844 return ((!WEP_CVAR(arc, beam_ammo)) || (actor.(thiswep.ammo_field) > 0)); 845} 846METHOD(Arc, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity)) 847{ 848 if(WEP_CVAR(arc, bolt)) 849 { 850 float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR(arc, bolt_ammo); 851 ammo_amount += actor.(weaponentity).(weapon_load[WEP_ARC.m_id]) >= WEP_CVAR(arc, bolt_ammo); 852 return ammo_amount; 853 } 854 else 855 return WEP_CVAR(arc, overheat_max) > 0 && 856 ((!WEP_CVAR(arc, burst_ammo)) || (actor.(thiswep.ammo_field) > 0)); 857} 858METHOD(Arc, wr_killmessage, Notification(entity thiswep)) 859{ 860 if(w_deathtype & HITTYPE_SECONDARY) 861 return WEAPON_ARC_MURDER_SPRAY; 862 else 863 return WEAPON_ARC_MURDER; 864} 865METHOD(Arc, wr_drop, void(entity thiswep, entity actor, .entity weaponentity)) 866{ 867 weapon_dropevent_item.arc_overheat = actor.arc_overheat; 868 weapon_dropevent_item.arc_cooldown = actor.arc_cooldown; 869 actor.arc_overheat = 0; 870 actor.arc_cooldown = 0; 871 actor.(weaponentity).arc_BUTTON_ATCK_prev = false; 872} 873METHOD(Arc, wr_pickup, void(entity thiswep, entity actor, .entity weaponentity)) 874{ 875 if ( !client_hasweapon(actor, thiswep, weaponentity, false, false) && 876 weapon_dropevent_item.arc_overheat > time ) 877 { 878 actor.arc_overheat = weapon_dropevent_item.arc_overheat; 879 actor.arc_cooldown = weapon_dropevent_item.arc_cooldown; 880 } 881} 882METHOD(Arc, wr_resetplayer, void(entity thiswep, entity actor)) 883{ 884 actor.arc_overheat = 0; 885 actor.arc_cooldown = 0; 886 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) 887 { 888 .entity weaponentity = weaponentities[slot]; 889 actor.(weaponentity).arc_BUTTON_ATCK_prev = false; 890 } 891} 892METHOD(Arc, wr_playerdeath, void(entity thiswep, entity actor, .entity weaponentity)) 893{ 894 actor.arc_overheat = 0; 895 actor.arc_cooldown = 0; 896 actor.(weaponentity).arc_BUTTON_ATCK_prev = false; 897} 898#endif 899#ifdef CSQC 900bool autocvar_cl_arcbeam_teamcolor = true; 901 902METHOD(Arc, wr_impacteffect, void(entity thiswep, entity actor)) 903{ 904 if(w_deathtype & HITTYPE_SECONDARY) 905 { 906 vector org2; 907 org2 = w_org + w_backoff * 6; 908 pointparticles(EFFECT_ARC_BOLT_EXPLODE, org2, w_backoff * 1000, 1); 909 if(!w_issilent) { sound(actor, CH_SHOTS, SND_LASERIMPACT, VOL_BASE, ATTN_NORM); } 910 } 911} 912 913void Draw_ArcBeam_callback(vector start, vector hit, vector end) 914{ 915 entity beam = Draw_ArcBeam_callback_entity; 916 vector transformed_view_org; 917 transformed_view_org = WarpZone_TransformOrigin(WarpZone_trace_transform, view_origin); 918 919 // Thickdir shall be perpendicular to the beam and to the view-to-beam direction (WEAPONTODO: WHY) 920 // WEAPONTODO: Wouldn't it be better to be perpendicular to the beam and to the view FORWARD direction? 921 vector thickdir = normalize(cross(normalize(start - hit), transformed_view_org - start)); 922 923 vector hitorigin; 924 925 // draw segment 926 #if 0 927 if(trace_fraction != 1) 928 { 929 // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?) 930 hitorigin = start + (Draw_ArcBeam_callback_new_dir * Draw_ArcBeam_callback_segmentdist * trace_fraction); 931 hitorigin = WarpZone_TransformOrigin(WarpZone_trace_transform, hitorigin); 932 } 933 else 934 { 935 hitorigin = hit; 936 } 937 #else 938 hitorigin = hit; 939 #endif 940 941 // decide upon thickness 942 float thickness = beam.beam_thickness; 943 944 // draw primary beam render 945 vector top = hitorigin + (thickdir * thickness); 946 vector bottom = hitorigin - (thickdir * thickness); 947 948 vector last_top = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_top); 949 vector last_bottom = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_bottom); 950 951 R_BeginPolygon(beam.beam_image, DRAWFLAG_NORMAL); // DRAWFLAG_ADDITIVE 952 R_PolygonVertex( 953 top, 954 '0 0.5 0' + ('0 0.5 0' * (thickness / beam.beam_thickness)), 955 beam.beam_color, 956 beam.beam_alpha 957 ); 958 R_PolygonVertex( 959 last_top, 960 '0 0.5 0' + ('0 0.5 0' * (Draw_ArcBeam_callback_last_thickness / beam.beam_thickness)), 961 beam.beam_color, 962 beam.beam_alpha 963 ); 964 R_PolygonVertex( 965 last_bottom, 966 '0 0.5 0' * (1 - (Draw_ArcBeam_callback_last_thickness / beam.beam_thickness)), 967 beam.beam_color, 968 beam.beam_alpha 969 ); 970 R_PolygonVertex( 971 bottom, 972 '0 0.5 0' * (1 - (thickness / beam.beam_thickness)), 973 beam.beam_color, 974 beam.beam_alpha 975 ); 976 R_EndPolygon(); 977 978 // draw trailing particles 979 // NOTES: 980 // - Don't use spammy particle counts here, use a FEW small particles around the beam 981 // - We're not using WarpZone_TrailParticles here because we will handle warpzones ourselves. 982 if(beam.beam_traileffect) 983 { 984 trailparticles(beam, beam.beam_traileffect, start, hitorigin); 985 } 986 987 // set up for the next 988 Draw_ArcBeam_callback_last_thickness = thickness; 989 Draw_ArcBeam_callback_last_top = WarpZone_UnTransformOrigin(WarpZone_trace_transform, top); 990 Draw_ArcBeam_callback_last_bottom = WarpZone_UnTransformOrigin(WarpZone_trace_transform, bottom); 991} 992 993void Reset_ArcBeam() 994{ 995 entity e; 996 for (e = NULL; (e = findfloat(e, beam_usevieworigin, 1)); ) { 997 e.beam_initialized = false; 998 } 999 for (e = NULL; (e = findfloat(e, beam_usevieworigin, 2)); ) { 1000 e.beam_initialized = false; 1001 } 1002} 1003 1004void Draw_ArcBeam(entity this) 1005{ 1006 float dt = time - this.move_time; 1007 this.move_time = time; 1008 if(dt <= 0) { return; } 1009 1010 if(!this.beam_usevieworigin) 1011 { 1012 InterpolateOrigin_Do(this); 1013 } 1014 1015 // origin = beam starting origin 1016 // v_angle = wanted/aim direction 1017 // angles = current direction of beam 1018 1019 vector start_pos; 1020 vector wantdir; //= view_forward; 1021 vector beamdir; //= this.beam_dir; 1022 1023 float segments; 1024 if(this.beam_usevieworigin) 1025 { 1026 // WEAPONTODO: 1027 // Currently we have to replicate nearly the same method of figuring 1028 // out the shotdir that the server does... Ideally in the future we 1029 // should be able to acquire this from a generalized function built 1030 // into a weapon system for client code. 1031 1032 // find where we are aiming 1033 makevectors(warpzone_save_view_angles); 1034 vector forward = v_forward; 1035 vector right = v_right; 1036 vector up = v_up; 1037 1038 // decide upon start position 1039 if(this.beam_usevieworigin == 2) 1040 { start_pos = warpzone_save_view_origin; } 1041 else 1042 { start_pos = this.origin; } 1043 1044 // trace forward with an estimation 1045 WarpZone_TraceLine( 1046 start_pos, 1047 start_pos + forward * this.beam_range, 1048 MOVE_NOMONSTERS, 1049 this 1050 ); 1051 1052 // untransform in case our trace went through a warpzone 1053 vector end_pos = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); 1054 1055 // un-adjust trueaim if shotend is too close 1056 if(vdist(end_pos - start_pos, <, g_trueaim_minrange)) 1057 end_pos = start_pos + (forward * g_trueaim_minrange); 1058 1059 // move shot origin to the actual gun muzzle origin 1060 vector origin_offset = 1061 right * -this.beam_shotorigin.y 1062 + up * this.beam_shotorigin.z; 1063 1064 start_pos = start_pos + origin_offset; 1065 1066 // Move it also forward, but only as far as possible without hitting anything. Don't poke into walls! 1067 traceline(start_pos, start_pos + forward * this.beam_shotorigin.x, MOVE_NORMAL, this); 1068 start_pos = trace_endpos; 1069 1070 // calculate the aim direction now 1071 wantdir = normalize(end_pos - start_pos); 1072 1073 if(!this.beam_initialized) 1074 { 1075 this.beam_dir = wantdir; 1076 this.beam_initialized = true; 1077 } 1078 1079 if(this.beam_dir != wantdir) 1080 { 1081 // calculate how much we're going to move the end of the beam to the want position 1082 // WEAPONTODO (server and client): 1083 // blendfactor never actually becomes 0 in this situation, which is a problem 1084 // regarding precision... this means that this.beam_dir and w_shotdir approach 1085 // eachother, however they never actually become the same value with this method. 1086 // Perhaps we should do some form of rounding/snapping? 1087 float angle = vlen(wantdir - this.beam_dir) * RAD2DEG; 1088 if(angle && (angle > this.beam_maxangle)) 1089 { 1090 // if the angle is greater than maxangle, force the blendfactor to make this the maximum factor 1091 float blendfactor = bound( 1092 0, 1093 (1 - (this.beam_returnspeed * frametime)), 1094 min(this.beam_maxangle / angle, 1) 1095 ); 1096 this.beam_dir = normalize((wantdir * (1 - blendfactor)) + (this.beam_dir * blendfactor)); 1097 } 1098 else 1099 { 1100 // the radius is not too far yet, no worries :D 1101 float blendfactor = bound( 1102 0, 1103 (1 - (this.beam_returnspeed * frametime)), 1104 1 1105 ); 1106 this.beam_dir = normalize((wantdir * (1 - blendfactor)) + (this.beam_dir * blendfactor)); 1107 } 1108 1109 // calculate how many segments are needed 1110 float max_allowed_segments; 1111 1112 if(this.beam_distancepersegment) 1113 { 1114 max_allowed_segments = min( 1115 ARC_MAX_SEGMENTS, 1116 1 + (vlen(wantdir / this.beam_distancepersegment)) 1117 ); 1118 } 1119 else { max_allowed_segments = ARC_MAX_SEGMENTS; } 1120 1121 if(this.beam_degreespersegment) 1122 { 1123 segments = bound( 1124 1, 1125 ( 1126 min( 1127 angle, 1128 this.beam_maxangle 1129 ) 1130 / 1131 this.beam_degreespersegment 1132 ), 1133 max_allowed_segments 1134 ); 1135 } 1136 else { segments = 1; } 1137 } 1138 else { segments = 1; } 1139 1140 // set the beam direction which the rest of the code will refer to 1141 beamdir = this.beam_dir; 1142 1143 // finally, set this.angles to the proper direction so that muzzle attachment points in proper direction 1144 this.angles = fixedvectoangles2(forward, up); // TODO(Samual): is this == warpzone_save_view_angles? 1145 } 1146 else 1147 { 1148 // set the values from the provided info from the networked entity 1149 start_pos = this.origin; 1150 wantdir = this.v_angle; 1151 beamdir = this.angles; 1152 1153 if(beamdir != wantdir) 1154 { 1155 float angle = vlen(wantdir - beamdir) * RAD2DEG; 1156 1157 // calculate how many segments are needed 1158 float max_allowed_segments; 1159 1160 if(this.beam_distancepersegment) 1161 { 1162 max_allowed_segments = min( 1163 ARC_MAX_SEGMENTS, 1164 1 + (vlen(wantdir / this.beam_distancepersegment)) 1165 ); 1166 } 1167 else { max_allowed_segments = ARC_MAX_SEGMENTS; } 1168 1169 if(this.beam_degreespersegment) 1170 { 1171 segments = bound( 1172 1, 1173 ( 1174 min( 1175 angle, 1176 this.beam_maxangle 1177 ) 1178 / 1179 this.beam_degreespersegment 1180 ), 1181 max_allowed_segments 1182 ); 1183 } 1184 else { segments = 1; } 1185 } 1186 else { segments = 1; } 1187 } 1188 1189 setorigin(this, start_pos); 1190 this.beam_muzzleentity.angles_z = random() * 360; // WEAPONTODO: use avelocity instead? 1191 1192 vector beam_endpos = (start_pos + (beamdir * this.beam_range)); 1193 vector beam_controlpoint = start_pos + wantdir * (this.beam_range * (1 - this.beam_tightness)); 1194 1195 Draw_ArcBeam_callback_entity = this; 1196 Draw_ArcBeam_callback_last_thickness = 0; 1197 Draw_ArcBeam_callback_last_top = start_pos; 1198 Draw_ArcBeam_callback_last_bottom = start_pos; 1199 1200 vector last_origin = start_pos; 1201 vector original_start_pos = start_pos; 1202 1203 float i; 1204 for(i = 1; i <= segments; ++i) 1205 { 1206 // WEAPONTODO (client): 1207 // In order to do nice fading and pointing on the starting segment, we must always 1208 // have that drawn as a separate triangle... However, that is difficult to do when 1209 // keeping in mind the above problems and also optimizing the amount of segments 1210 // drawn on screen at any given time. (Automatic beam quality scaling, essentially) 1211 1212 vector new_origin = bezier_quadratic_getpoint( 1213 start_pos, 1214 beam_controlpoint, 1215 beam_endpos, 1216 i / segments); 1217 1218 WarpZone_TraceBox_ThroughZone( 1219 last_origin, 1220 '0 0 0', 1221 '0 0 0', 1222 new_origin, 1223 MOVE_NORMAL, 1224 NULL, 1225 NULL, 1226 Draw_ArcBeam_callback 1227 ); 1228 1229 // Do all the transforms for warpzones right now, as we already "are" in the post-trace 1230 // system (if we hit a player, that's always BEHIND the last passed wz). 1231 last_origin = trace_endpos; 1232 start_pos = WarpZone_TransformOrigin(WarpZone_trace_transform, start_pos); 1233 beam_controlpoint = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_controlpoint); 1234 beam_endpos = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_endpos); 1235 beamdir = WarpZone_TransformVelocity(WarpZone_trace_transform, beamdir); 1236 Draw_ArcBeam_callback_last_top = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_top); 1237 Draw_ArcBeam_callback_last_bottom = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_bottom); 1238 1239 if(trace_fraction < 1) { break; } 1240 } 1241 1242 // visual effects for startpoint and endpoint 1243 if(this.beam_hiteffect) 1244 { 1245 // FIXME we really should do this on the server so it actually 1246 // matches gameplay. What this client side stuff is doing is no 1247 // more than guesswork. 1248 if((trace_ent || trace_fraction < 1) && !(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)) 1249 pointparticles( 1250 this.beam_hiteffect, 1251 last_origin, 1252 beamdir * -1, 1253 frametime * 2 1254 ); 1255 } 1256 if(this.beam_hitlight[0]) 1257 { 1258 adddynamiclight( 1259 last_origin, 1260 this.beam_hitlight[0], 1261 vec3( 1262 this.beam_hitlight[1], 1263 this.beam_hitlight[2], 1264 this.beam_hitlight[3] 1265 ) 1266 ); 1267 } 1268 if(this.beam_muzzleeffect) 1269 { 1270 pointparticles( 1271 this.beam_muzzleeffect, 1272 original_start_pos + wantdir * 20, 1273 wantdir * 1000, 1274 frametime * 0.1 1275 ); 1276 } 1277 if(this.beam_muzzlelight[0]) 1278 { 1279 adddynamiclight( 1280 original_start_pos + wantdir * 20, 1281 this.beam_muzzlelight[0], 1282 vec3( 1283 this.beam_muzzlelight[1], 1284 this.beam_muzzlelight[2], 1285 this.beam_muzzlelight[3] 1286 ) 1287 ); 1288 } 1289 1290 // cleanup 1291 Draw_ArcBeam_callback_entity = NULL; 1292 Draw_ArcBeam_callback_last_thickness = 0; 1293 Draw_ArcBeam_callback_last_top = '0 0 0'; 1294 Draw_ArcBeam_callback_last_bottom = '0 0 0'; 1295} 1296 1297void Remove_ArcBeam(entity this) 1298{ 1299 delete(this.beam_muzzleentity); 1300 sound(this, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, ATTEN_NORM); 1301} 1302 1303NET_HANDLE(ENT_CLIENT_ARC_BEAM, bool isnew) 1304{ 1305 int sf = ReadByte(); 1306 int slot = ReadByte(); 1307 entity flash; 1308 1309 if(isnew) 1310 { 1311 int gunalign = W_GunAlign(viewmodels[slot], STAT(GUNALIGN)) - 1; 1312 1313 this.beam_shotorigin = arc_shotorigin[gunalign]; 1314 1315 // set other main attributes of the beam 1316 this.draw = Draw_ArcBeam; 1317 IL_PUSH(g_drawables, this); 1318 this.entremove = Remove_ArcBeam; 1319 this.move_time = time; 1320 loopsound(this, CH_SHOTS_SINGLE, SND(ARC_LOOP), VOL_BASE, ATTEN_NORM); 1321 1322 flash = spawn(); 1323 flash.owner = this; 1324 flash.effects = EF_ADDITIVE | EF_FULLBRIGHT; 1325 flash.drawmask = MASK_NORMAL; 1326 flash.solid = SOLID_NOT; 1327 flash.avelocity_z = 5000; 1328 setattachment(flash, this, ""); 1329 setorigin(flash, '0 0 0'); 1330 1331 this.beam_muzzleentity = flash; 1332 } 1333 else 1334 { 1335 flash = this.beam_muzzleentity; 1336 } 1337 1338 if(sf & ARC_SF_SETTINGS) // settings information 1339 { 1340 this.beam_degreespersegment = ReadShort(); 1341 this.beam_distancepersegment = ReadShort(); 1342 this.beam_maxangle = ReadShort(); 1343 this.beam_range = ReadCoord(); 1344 this.beam_returnspeed = ReadShort(); 1345 this.beam_tightness = (ReadByte() / 10); 1346 1347 if(ReadByte()) 1348 { 1349 if(autocvar_chase_active) 1350 { this.beam_usevieworigin = 1; } 1351 else // use view origin 1352 { this.beam_usevieworigin = 2; } 1353 } 1354 else 1355 { 1356 this.beam_usevieworigin = 0; 1357 } 1358 1359 this.sv_entnum = ReadByte(); 1360 } 1361 1362 if(!this.beam_usevieworigin) 1363 { 1364 // this.iflags = IFLAG_ORIGIN | IFLAG_ANGLES | IFLAG_V_ANGLE; // why doesn't this work? 1365 this.iflags = IFLAG_ORIGIN; 1366 1367 InterpolateOrigin_Undo(this); 1368 } 1369 1370 if(sf & ARC_SF_START) // starting location 1371 { 1372 this.origin_x = ReadCoord(); 1373 this.origin_y = ReadCoord(); 1374 this.origin_z = ReadCoord(); 1375 } 1376 else if(this.beam_usevieworigin) // infer the location from player location 1377 { 1378 if(this.beam_usevieworigin == 2) 1379 { 1380 // use view origin 1381 this.origin = view_origin; 1382 } 1383 else 1384 { 1385 // use player origin so that third person display still works 1386 this.origin = entcs_receiver(player_localnum).origin + ('0 0 1' * STAT(VIEWHEIGHT)); 1387 } 1388 } 1389 1390 setorigin(this, this.origin); 1391 1392 if(sf & ARC_SF_WANTDIR) // want/aim direction 1393 { 1394 this.v_angle_x = ReadCoord(); 1395 this.v_angle_y = ReadCoord(); 1396 this.v_angle_z = ReadCoord(); 1397 } 1398 1399 if(sf & ARC_SF_BEAMDIR) // beam direction 1400 { 1401 this.angles_x = ReadCoord(); 1402 this.angles_y = ReadCoord(); 1403 this.angles_z = ReadCoord(); 1404 } 1405 1406 if(sf & ARC_SF_BEAMTYPE) // beam type 1407 { 1408 this.beam_type = ReadByte(); 1409 1410 vector beamcolor = ((autocvar_cl_arcbeam_teamcolor) ? colormapPaletteColor(entcs_GetClientColors(this.sv_entnum - 1) & 0x0F, true) : '1 1 1'); 1411 switch(this.beam_type) 1412 { 1413 case ARC_BT_MISS: 1414 { 1415 this.beam_color = beamcolor; 1416 this.beam_alpha = 0.5; 1417 this.beam_thickness = 8; 1418 this.beam_traileffect = (EFFECT_ARC_BEAM); 1419 this.beam_hiteffect = (EFFECT_ARC_LIGHTNING); 1420 this.beam_hitlight[0] = 0; 1421 this.beam_hitlight[1] = 1; 1422 this.beam_hitlight[2] = 1; 1423 this.beam_hitlight[3] = 1; 1424 this.beam_muzzleeffect = NULL; //(EFFECT_VORTEX_MUZZLEFLASH); 1425 this.beam_muzzlelight[0] = 0; 1426 this.beam_muzzlelight[1] = 1; 1427 this.beam_muzzlelight[2] = 1; 1428 this.beam_muzzlelight[3] = 1; 1429 if(this.beam_muzzleeffect) 1430 { 1431 setmodel(flash, MDL_ARC_MUZZLEFLASH); 1432 flash.alpha = this.beam_alpha; 1433 flash.colormod = this.beam_color; 1434 flash.scale = 0.5; 1435 } 1436 break; 1437 } 1438 case ARC_BT_WALL: // grenadelauncher_muzzleflash healray_muzzleflash 1439 { 1440 this.beam_color = beamcolor; 1441 this.beam_alpha = 0.5; 1442 this.beam_thickness = 8; 1443 this.beam_traileffect = (EFFECT_ARC_BEAM); 1444 this.beam_hiteffect = (EFFECT_ARC_LIGHTNING); 1445 this.beam_hitlight[0] = 0; 1446 this.beam_hitlight[1] = 1; 1447 this.beam_hitlight[2] = 1; 1448 this.beam_hitlight[3] = 1; 1449 this.beam_muzzleeffect = NULL; // (EFFECT_GRENADE_MUZZLEFLASH); 1450 this.beam_muzzlelight[0] = 0; 1451 this.beam_muzzlelight[1] = 1; 1452 this.beam_muzzlelight[2] = 1; 1453 this.beam_muzzlelight[3] = 1; 1454 this.beam_image = "particles/lgbeam"; 1455 if(this.beam_muzzleeffect) 1456 { 1457 setmodel(flash, MDL_ARC_MUZZLEFLASH); 1458 flash.alpha = this.beam_alpha; 1459 flash.colormod = this.beam_color; 1460 flash.scale = 0.5; 1461 } 1462 break; 1463 } 1464 case ARC_BT_HEAL: 1465 { 1466 this.beam_color = beamcolor; 1467 this.beam_alpha = 0.5; 1468 this.beam_thickness = 8; 1469 this.beam_traileffect = (EFFECT_ARC_BEAM_HEAL); 1470 this.beam_hiteffect = (EFFECT_ARC_BEAM_HEAL_IMPACT); 1471 this.beam_hitlight[0] = 0; 1472 this.beam_hitlight[1] = 1; 1473 this.beam_hitlight[2] = 1; 1474 this.beam_hitlight[3] = 1; 1475 this.beam_muzzleeffect = NULL; //(EFFECT_VORTEX_MUZZLEFLASH); 1476 this.beam_muzzlelight[0] = 0; 1477 this.beam_muzzlelight[1] = 1; 1478 this.beam_muzzlelight[2] = 1; 1479 this.beam_muzzlelight[3] = 1; 1480 this.beam_image = "particles/lgbeam"; 1481 if(this.beam_muzzleeffect) 1482 { 1483 setmodel(flash, MDL_ARC_MUZZLEFLASH); 1484 flash.alpha = this.beam_alpha; 1485 flash.colormod = this.beam_color; 1486 flash.scale = 0.5; 1487 } 1488 break; 1489 } 1490 case ARC_BT_HIT: 1491 { 1492 this.beam_color = beamcolor; 1493 this.beam_alpha = 0.5; 1494 this.beam_thickness = 8; 1495 this.beam_traileffect = (EFFECT_ARC_BEAM); 1496 this.beam_hiteffect = (EFFECT_ARC_LIGHTNING); 1497 this.beam_hitlight[0] = 20; 1498 this.beam_hitlight[1] = 1; 1499 this.beam_hitlight[2] = 0; 1500 this.beam_hitlight[3] = 0; 1501 this.beam_muzzleeffect = NULL; //(EFFECT_VORTEX_MUZZLEFLASH); 1502 this.beam_muzzlelight[0] = 50; 1503 this.beam_muzzlelight[1] = 1; 1504 this.beam_muzzlelight[2] = 0; 1505 this.beam_muzzlelight[3] = 0; 1506 this.beam_image = "particles/lgbeam"; 1507 if(this.beam_muzzleeffect) 1508 { 1509 setmodel(flash, MDL_ARC_MUZZLEFLASH); 1510 flash.alpha = this.beam_alpha; 1511 flash.colormod = this.beam_color; 1512 flash.scale = 0.5; 1513 } 1514 break; 1515 } 1516 case ARC_BT_BURST_MISS: 1517 { 1518 this.beam_color = beamcolor; 1519 this.beam_alpha = 0.5; 1520 this.beam_thickness = 14; 1521 this.beam_traileffect = (EFFECT_ARC_BEAM); 1522 this.beam_hiteffect = (EFFECT_ARC_LIGHTNING); 1523 this.beam_hitlight[0] = 0; 1524 this.beam_hitlight[1] = 1; 1525 this.beam_hitlight[2] = 1; 1526 this.beam_hitlight[3] = 1; 1527 this.beam_muzzleeffect = NULL; //(EFFECT_VORTEX_MUZZLEFLASH); 1528 this.beam_muzzlelight[0] = 0; 1529 this.beam_muzzlelight[1] = 1; 1530 this.beam_muzzlelight[2] = 1; 1531 this.beam_muzzlelight[3] = 1; 1532 this.beam_image = "particles/lgbeam"; 1533 if(this.beam_muzzleeffect) 1534 { 1535 setmodel(flash, MDL_ARC_MUZZLEFLASH); 1536 flash.alpha = this.beam_alpha; 1537 flash.colormod = this.beam_color; 1538 flash.scale = 0.5; 1539 } 1540 break; 1541 } 1542 case ARC_BT_BURST_WALL: 1543 { 1544 this.beam_color = beamcolor; 1545 this.beam_alpha = 0.5; 1546 this.beam_thickness = 14; 1547 this.beam_traileffect = (EFFECT_ARC_BEAM); 1548 this.beam_hiteffect = (EFFECT_ARC_LIGHTNING); 1549 this.beam_hitlight[0] = 0; 1550 this.beam_hitlight[1] = 1; 1551 this.beam_hitlight[2] = 1; 1552 this.beam_hitlight[3] = 1; 1553 this.beam_muzzleeffect = NULL; //(EFFECT_VORTEX_MUZZLEFLASH); 1554 this.beam_muzzlelight[0] = 0; 1555 this.beam_muzzlelight[1] = 1; 1556 this.beam_muzzlelight[2] = 1; 1557 this.beam_muzzlelight[3] = 1; 1558 this.beam_image = "particles/lgbeam"; 1559 if(this.beam_muzzleeffect) 1560 { 1561 setmodel(flash, MDL_ARC_MUZZLEFLASH); 1562 flash.alpha = this.beam_alpha; 1563 flash.colormod = this.beam_color; 1564 flash.scale = 0.5; 1565 } 1566 break; 1567 } 1568 case ARC_BT_BURST_HEAL: 1569 { 1570 this.beam_color = beamcolor; 1571 this.beam_alpha = 0.5; 1572 this.beam_thickness = 14; 1573 this.beam_traileffect = (EFFECT_ARC_BEAM_HEAL); 1574 this.beam_hiteffect = (EFFECT_ARC_BEAM_HEAL_IMPACT2); 1575 this.beam_hitlight[0] = 0; 1576 this.beam_hitlight[1] = 1; 1577 this.beam_hitlight[2] = 1; 1578 this.beam_hitlight[3] = 1; 1579 this.beam_muzzleeffect = NULL; //(EFFECT_VORTEX_MUZZLEFLASH); 1580 this.beam_muzzlelight[0] = 0; 1581 this.beam_muzzlelight[1] = 1; 1582 this.beam_muzzlelight[2] = 1; 1583 this.beam_muzzlelight[3] = 1; 1584 this.beam_image = "particles/lgbeam"; 1585 if(this.beam_muzzleeffect) 1586 { 1587 setmodel(flash, MDL_ARC_MUZZLEFLASH); 1588 flash.alpha = this.beam_alpha; 1589 flash.colormod = this.beam_color; 1590 flash.scale = 0.5; 1591 } 1592 break; 1593 } 1594 case ARC_BT_BURST_HIT: 1595 { 1596 this.beam_color = beamcolor; 1597 this.beam_alpha = 0.5; 1598 this.beam_thickness = 14; 1599 this.beam_traileffect = (EFFECT_ARC_BEAM); 1600 this.beam_hiteffect = (EFFECT_ARC_LIGHTNING); 1601 this.beam_hitlight[0] = 0; 1602 this.beam_hitlight[1] = 1; 1603 this.beam_hitlight[2] = 1; 1604 this.beam_hitlight[3] = 1; 1605 this.beam_muzzleeffect = NULL; //(EFFECT_VORTEX_MUZZLEFLASH); 1606 this.beam_muzzlelight[0] = 0; 1607 this.beam_muzzlelight[1] = 1; 1608 this.beam_muzzlelight[2] = 1; 1609 this.beam_muzzlelight[3] = 1; 1610 this.beam_image = "particles/lgbeam"; 1611 if(this.beam_muzzleeffect) 1612 { 1613 setmodel(flash, MDL_ARC_MUZZLEFLASH); 1614 flash.alpha = this.beam_alpha; 1615 flash.colormod = this.beam_color; 1616 flash.scale = 0.5; 1617 } 1618 break; 1619 } 1620 1621 // shouldn't be possible, but lets make it colorful if it does :D 1622 default: 1623 { 1624 this.beam_color = randomvec(); 1625 this.beam_alpha = 1; 1626 this.beam_thickness = 8; 1627 this.beam_traileffect = NULL; 1628 this.beam_hiteffect = NULL; 1629 this.beam_hitlight[0] = 0; 1630 this.beam_hitlight[1] = 1; 1631 this.beam_hitlight[2] = 1; 1632 this.beam_hitlight[3] = 1; 1633 this.beam_muzzleeffect = NULL; //(EFFECT_VORTEX_MUZZLEFLASH); 1634 this.beam_muzzlelight[0] = 0; 1635 this.beam_muzzlelight[1] = 1; 1636 this.beam_muzzlelight[2] = 1; 1637 this.beam_muzzlelight[3] = 1; 1638 this.beam_image = "particles/lgbeam"; 1639 if(this.beam_muzzleeffect) 1640 { 1641 setmodel(flash, MDL_ARC_MUZZLEFLASH); 1642 flash.alpha = this.beam_alpha; 1643 flash.colormod = this.beam_color; 1644 flash.scale = 0.5; 1645 } 1646 break; 1647 } 1648 } 1649 } 1650 1651 if(!this.beam_usevieworigin) 1652 { 1653 InterpolateOrigin_Note(this); 1654 } 1655 return true; 1656} 1657 1658#endif 1659#endif 1660