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