]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/weapons/w_arc.qc
a83b1be826351be908897c232a4c40f7e79dbba4
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / weapons / w_arc.qc
1 #ifdef REGISTER_WEAPON
2 REGISTER_WEAPON(
3 /* WEP_##id  */ ARC,
4 /* function  */ W_Arc,
5 /* ammotype  */ ammo_cells,
6 /* impulse   */ 3,
7 /* flags     */ WEP_FLAG_NORMAL,
8 /* rating    */ BOT_PICKUP_RATING_HIGH,
9 /* color     */ '1 1 1',
10 /* modelname */ "hlac",
11 /* simplemdl */ "foobar",
12 /* crosshair */ "gfx/crosshairhlac 0.7",
13 /* wepimg    */ "weaponhlac",
14 /* refname   */ "arc",
15 /* wepname   */ _("Arc")
16 );
17
18 #define ARC_SETTINGS(w_cvar,w_prop) ARC_SETTINGS_LIST(w_cvar, w_prop, ARC, arc)
19 #define ARC_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
20         w_cvar(id, sn, NONE, beam_ammo) \
21         w_cvar(id, sn, NONE, beam_animtime) \
22         w_cvar(id, sn, NONE, beam_botaimspeed) \
23         w_cvar(id, sn, NONE, beam_botaimlifetime) \
24         w_cvar(id, sn, NONE, beam_damage) \
25         w_cvar(id, sn, NONE, beam_degreespersegment) \
26         w_cvar(id, sn, NONE, beam_distancepersegment) \
27         w_cvar(id, sn, NONE, beam_falloff_halflifedist) \
28         w_cvar(id, sn, NONE, beam_falloff_maxdist) \
29         w_cvar(id, sn, NONE, beam_falloff_mindist) \
30         w_cvar(id, sn, NONE, beam_force) \
31         w_cvar(id, sn, NONE, beam_healing_amax) \
32         w_cvar(id, sn, NONE, beam_healing_aps) \
33         w_cvar(id, sn, NONE, beam_healing_hmax) \
34         w_cvar(id, sn, NONE, beam_healing_hps) \
35         w_cvar(id, sn, NONE, beam_maxangle) \
36         w_cvar(id, sn, NONE, beam_nonplayerdamage) \
37         w_cvar(id, sn, NONE, beam_range) \
38         w_cvar(id, sn, NONE, beam_refire) \
39         w_cvar(id, sn, NONE, beam_returnspeed) \
40         w_cvar(id, sn, NONE, beam_tightness) \
41         w_cvar(id, sn, NONE, burst_ammo) \
42         w_cvar(id, sn, NONE, burst_damage) \
43         w_cvar(id, sn, NONE, burst_healing_aps) \
44         w_cvar(id, sn, NONE, burst_healing_hps) \
45         w_prop(id, sn, float,  switchdelay_raise, switchdelay_raise) \
46         w_prop(id, sn, float,  switchdelay_drop, switchdelay_drop) \
47         w_prop(id, sn, string, weaponreplace, weaponreplace) \
48         w_prop(id, sn, float,  weaponstart, weaponstart) \
49         w_prop(id, sn, float,  weaponstartoverride, weaponstartoverride) \
50         w_prop(id, sn, float,  weaponthrowable, weaponthrowable)
51
52 #ifndef MENUQC
53 #define ARC_MAX_SEGMENTS 20
54 vector arc_shotorigin[4];
55 .vector beam_start;
56 .vector beam_dir;
57 .vector beam_wantdir;
58 .float beam_type;
59
60 #define ARC_BT_MISS        0
61 #define ARC_BT_WALL        1
62 #define ARC_BT_HEAL        2
63 #define ARC_BT_HIT         3
64 #define ARC_BT_BURST_MISS  10
65 #define ARC_BT_BURST_WALL  11
66 #define ARC_BT_BURST_HEAL  12
67 #define ARC_BT_BURST_HIT   13
68 #define ARC_BT_BURSTMASK   10
69
70 #define ARC_SF_SETTINGS    1
71 #define ARC_SF_START       2
72 #define ARC_SF_WANTDIR     4
73 #define ARC_SF_BEAMDIR     8
74 #define ARC_SF_BEAMTYPE    16
75 #define ARC_SF_LOCALMASK   14
76 #endif
77 #ifdef SVQC
78 ARC_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
79 .entity arc_beam;
80 .float BUTTON_ATCK_prev; // for better animation control
81 .float beam_prev;
82 .float beam_initialized;
83 .float beam_bursting;
84 #endif
85 #ifdef CSQC
86 void Ent_ReadArcBeam(float isnew);
87 #endif
88 #else
89 #ifdef SVQC
90 void spawnfunc_weapon_arc(void) { weapon_defaultspawnfunc(WEP_ARC); }
91
92 float W_Arc_Beam_Send(entity to, float sf)
93 {
94         WriteByte(MSG_ENTITY, ENT_CLIENT_ARC_BEAM);
95
96         // Truncate information when this beam is displayed to the owner client
97         // - The owner client has no use for beam start position or directions,
98         //    it always figures this information out for itself with csqc code.
99         // - Spectating the owner also truncates this information.
100         float drawlocal = ((to == self.owner) || ((to.enemy == self.owner) && IS_SPEC(to)));
101         if(drawlocal) { sf &= ~ARC_SF_LOCALMASK; }
102
103         WriteByte(MSG_ENTITY, sf);
104
105         if(sf & ARC_SF_SETTINGS) // settings information
106         {
107                 WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_degreespersegment));
108                 WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_distancepersegment));
109                 WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_maxangle));
110                 WriteCoord(MSG_ENTITY, WEP_CVAR(arc, beam_range));
111                 WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_returnspeed));
112                 WriteByte(MSG_ENTITY, WEP_CVAR(arc, beam_tightness) * 10);
113
114                 WriteByte(MSG_ENTITY, drawlocal);
115         }
116         if(sf & ARC_SF_START) // starting location
117         {
118                 WriteCoord(MSG_ENTITY, self.beam_start_x);
119                 WriteCoord(MSG_ENTITY, self.beam_start_y);
120                 WriteCoord(MSG_ENTITY, self.beam_start_z);
121         }
122         if(sf & ARC_SF_WANTDIR) // want/aim direction
123         {
124                 WriteCoord(MSG_ENTITY, self.beam_wantdir_x);
125                 WriteCoord(MSG_ENTITY, self.beam_wantdir_y);
126                 WriteCoord(MSG_ENTITY, self.beam_wantdir_z);
127         }
128         if(sf & ARC_SF_BEAMDIR) // beam direction
129         {
130                 WriteCoord(MSG_ENTITY, self.beam_dir_x);
131                 WriteCoord(MSG_ENTITY, self.beam_dir_y);
132                 WriteCoord(MSG_ENTITY, self.beam_dir_z);
133         }
134         if(sf & ARC_SF_BEAMTYPE) // beam type
135         {
136                 WriteByte(MSG_ENTITY, self.beam_type);
137         }
138
139         return TRUE;
140 }
141
142 void W_Arc_Beam_Think(void)
143 {
144         if(self != self.owner.arc_beam)
145         {
146                 remove(self);
147                 return;
148         }
149
150         if(
151                 (self.owner.WEP_AMMO(ARC) <= 0 && !(self.owner.items & IT_UNLIMITED_WEAPON_AMMO))
152                 ||
153                 self.owner.deadflag != DEAD_NO
154                 ||
155                 (!self.owner.BUTTON_ATCK && !self.beam_bursting)
156                 ||
157                 self.owner.freezetag_frozen
158         )
159         {
160                 #if 0
161                 // is this needed? I thought this is changed to world when removed ANYWAY
162                 if(self == self.owner.arc_beam) { self.owner.arc_beam = world; }
163                 #endif
164                 entity oldself = self;
165                 self = self.owner;
166                 if(!WEP_ACTION(WEP_ARC, WR_CHECKAMMO1) && !WEP_ACTION(WEP_ARC, WR_CHECKAMMO2))
167                 {
168                         // note: this doesn't force the switch
169                         W_SwitchToOtherWeapon(self);
170                 }
171                 self = oldself;
172                 remove(self);
173                 return;
174         }
175
176         float burst = 0;
177         if(self.owner.BUTTON_ATCK2 || self.beam_bursting)
178         {
179                 if(!self.beam_bursting)
180                         self.beam_bursting = TRUE;
181                 burst = ARC_BT_BURSTMASK;
182         }
183
184         // decrease ammo
185         float coefficient = frametime;
186         if(!(self.owner.items & IT_UNLIMITED_WEAPON_AMMO))
187         {
188                 float rootammo;
189                 if(burst)
190                         { rootammo = WEP_CVAR(arc, burst_ammo); }
191                 else
192                         { rootammo = WEP_CVAR(arc, beam_ammo); }
193
194                 if(rootammo)
195                 {
196                         coefficient = min(coefficient, self.owner.WEP_AMMO(ARC) / rootammo);
197                         self.owner.WEP_AMMO(ARC) = max(0, self.owner.WEP_AMMO(ARC) - (rootammo * frametime));
198                 }
199         }
200
201         makevectors(self.owner.v_angle);
202
203         W_SetupShot_Range(
204                 self.owner,
205                 TRUE,
206                 0,
207                 "",
208                 0,
209                 WEP_CVAR(arc, beam_damage) * coefficient,
210                 WEP_CVAR(arc, beam_range)
211         );
212
213         // network information: shot origin and want/aim direction
214         if(self.beam_start != w_shotorg)
215         {
216                 self.SendFlags |= ARC_SF_START;
217                 self.beam_start = w_shotorg;
218         }
219         if(self.beam_wantdir != w_shotdir)
220         {
221                 self.SendFlags |= ARC_SF_WANTDIR;
222                 self.beam_wantdir = w_shotdir;
223         }
224
225         if(!self.beam_initialized)
226         {
227                 self.beam_dir = w_shotdir;
228                 self.beam_initialized = TRUE;
229         }
230
231         // WEAPONTODO: Detect player velocity so that the beam curves when moving too
232         // idea: blend together self.beam_dir with the inverted direction the player is moving in
233         // might have to make some special accomodation so that it only uses view_right and view_up
234
235         // note that if we do this, it'll always be corrected to a maximum angle by beam_maxangle handling
236
237         float segments; 
238         if(self.beam_dir != w_shotdir)
239         {
240                 // calculate how much we're going to move the end of the beam to the want position
241                 // WEAPONTODO (server and client):
242                 // blendfactor never actually becomes 0 in this situation, which is a problem
243                 // regarding precision... this means that self.beam_dir and w_shotdir approach
244                 // eachother, however they never actually become the same value with this method.
245                 // Perhaps we should do some form of rounding/snapping?
246                 float angle = vlen(w_shotdir - self.beam_dir) * RAD2DEG;
247                 if(angle && (angle > WEP_CVAR(arc, beam_maxangle)))
248                 {
249                         // if the angle is greater than maxangle, force the blendfactor to make this the maximum factor
250                         float blendfactor = bound(
251                                 0,
252                                 (1 - (WEP_CVAR(arc, beam_returnspeed) * frametime)),
253                                 min(WEP_CVAR(arc, beam_maxangle) / angle, 1)
254                         );
255                         self.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (self.beam_dir * blendfactor));
256                 }
257                 else
258                 {
259                         // the radius is not too far yet, no worries :D
260                         float blendfactor = bound(
261                                 0,
262                                 (1 - (WEP_CVAR(arc, beam_returnspeed) * frametime)),
263                                 1
264                         );
265                         self.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (self.beam_dir * blendfactor));
266                 }
267
268                 // network information: beam direction
269                 self.SendFlags |= ARC_SF_BEAMDIR;
270
271                 // calculate how many segments are needed
272                 float max_allowed_segments;
273
274                 if(WEP_CVAR(arc, beam_distancepersegment))
275                 {
276                         max_allowed_segments = min(
277                                 ARC_MAX_SEGMENTS,
278                                 1 + (vlen(w_shotdir / WEP_CVAR(arc, beam_distancepersegment)))
279                         );
280                 }
281                 else { max_allowed_segments = ARC_MAX_SEGMENTS; }
282
283                 if(WEP_CVAR(arc, beam_degreespersegment))
284                 {
285                         segments = bound(
286                                 1, 
287                                 (
288                                         min(
289                                                 angle,
290                                                 WEP_CVAR(arc, beam_maxangle)
291                                         )
292                                         /
293                                         WEP_CVAR(arc, beam_degreespersegment)
294                                 ),
295                                 max_allowed_segments
296                         );
297                 }
298                 else { segments = 1; }
299         }
300         else { segments = 1; }
301
302         vector beam_endpos_estimate = (w_shotorg + (self.beam_dir * WEP_CVAR(arc, beam_range)));
303
304         float i;
305         float new_beam_type = 0;
306         vector last_origin = w_shotorg;
307         for(i = 1; i <= segments; ++i)
308         {
309                 // WEAPONTODO (server and client):
310                 // Segment blend and distance should probably really be calculated in a better way,
311                 // however I am not sure how to do it properly. There are a few things I have tried,
312                 // but most of them do not work properly due to my lack of understanding regarding
313                 // the mathematics behind them.
314
315                 // Ideally, we should calculate the positions along a perfect curve
316                 // between wantdir and self.beam_dir with an option for depth of arc
317
318                 // Another issue is that (on the client code) we must separate the
319                 // curve into multiple rendered curves when handling warpzones.
320
321                 // I can handle this by detecting it for each segment, however that
322                 // is a fairly inefficient method in comparison to having a curved line
323                 // drawing function similar to Draw_CylindricLine that accepts
324                 // top and bottom origins as input, this way there would be no
325                 // overlapping edges when connecting the curved pieces.
326
327                 // WEAPONTODO (client):
328                 // In order to do nice fading and pointing on the starting segment, we must always
329                 // have that drawn as a separate triangle... However, that is difficult to do when
330                 // keeping in mind the above problems and also optimizing the amount of segments
331                 // drawn on screen at any given time. (Automatic beam quality scaling, essentially)
332
333                 // calculate this on every segment to ensure that we always reach the full length of the attack
334                 float segmentblend = bound(0, (i/segments) + WEP_CVAR(arc, beam_tightness), 1);
335                 float segmentdist = vlen(beam_endpos_estimate - last_origin) * (i/segments);
336
337                 // WEAPONTODO: Apparently, normalize is not the correct function to use here...
338                 // Figure out how this actually should work.
339                 vector new_dir = normalize(
340                         (w_shotdir * (1 - segmentblend))
341                         +
342                         (normalize(beam_endpos_estimate - last_origin) * segmentblend)
343                 );
344                 vector new_origin = last_origin + (new_dir * segmentdist);
345
346                 WarpZone_traceline_antilag(
347                         self.owner,
348                         last_origin,
349                         new_origin,
350                         MOVE_NORMAL,
351                         self.owner,
352                         ANTILAG_LATENCY(self.owner)
353                 );
354
355                 float is_player = (
356                         trace_ent.classname == "player"
357                         ||
358                         trace_ent.classname == "body"
359                         ||
360                         (trace_ent.flags & FL_MONSTER)
361                 );
362
363                 if(trace_ent && trace_ent.takedamage && (is_player || WEP_CVAR(arc, beam_nonplayerdamage)))
364                 {
365                         // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?)
366                         vector hitorigin = last_origin + (new_dir * segmentdist * trace_fraction);
367
368                         float falloff = ExponentialFalloff(
369                                 WEP_CVAR(arc, beam_falloff_mindist),
370                                 WEP_CVAR(arc, beam_falloff_maxdist),
371                                 WEP_CVAR(arc, beam_falloff_halflifedist),
372                                 vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, hitorigin) - w_shotorg)
373                         );
374
375                         if(is_player && SAME_TEAM(self.owner, trace_ent))
376                         {
377                                 float roothealth, rootarmor;
378                                 if(burst)
379                                 {
380                                         roothealth = WEP_CVAR(arc, burst_healing_hps);
381                                         rootarmor = WEP_CVAR(arc, burst_healing_aps);
382                                 }
383                                 else
384                                 {
385                                         roothealth = WEP_CVAR(arc, beam_healing_hps);
386                                         rootarmor = WEP_CVAR(arc, beam_healing_aps);
387                                 }
388
389                                 if(trace_ent.health <= WEP_CVAR(arc, beam_healing_hmax) && roothealth)
390                                 {
391                                         trace_ent.health = min(
392                                                 trace_ent.health + (roothealth * coefficient),
393                                                 WEP_CVAR(arc, beam_healing_hmax)
394                                         );
395                                 }
396                                 if(trace_ent.armorvalue <= WEP_CVAR(arc, beam_healing_amax) && rootarmor)
397                                 {
398                                         trace_ent.armorvalue = min(
399                                                 trace_ent.armorvalue + (rootarmor * coefficient),
400                                                 WEP_CVAR(arc, beam_healing_amax)
401                                         );
402                                 }
403
404                                 // stop rot, set visual effect
405                                 if(roothealth || rootarmor)
406                                 {
407                                         trace_ent.pauserothealth_finished = max(
408                                                 trace_ent.pauserothealth_finished,
409                                                 time + autocvar_g_balance_pause_health_rot
410                                         );
411                                         trace_ent.pauserotarmor_finished = max(
412                                                 trace_ent.pauserotarmor_finished,
413                                                 time + autocvar_g_balance_pause_armor_rot
414                                         );
415                                         new_beam_type = ARC_BT_HEAL;
416                                 }
417                         }
418                         else
419                         {
420                                 float rootdamage;
421                                 if(is_player)
422                                 {
423                                         if(burst)
424                                                 { rootdamage = WEP_CVAR(arc, burst_damage); }
425                                         else
426                                                 { rootdamage = WEP_CVAR(arc, beam_damage); }
427                                 }
428                                 else
429                                         { rootdamage = WEP_CVAR(arc, beam_nonplayerdamage); }
430
431                                 if(accuracy_isgooddamage(self.owner, trace_ent))
432                                 {
433                                         accuracy_add(
434                                                 self.owner,
435                                                 WEP_ARC,
436                                                 0,
437                                                 rootdamage * coefficient * falloff
438                                         );
439                                 }
440
441                                 Damage(
442                                         trace_ent,
443                                         self.owner,
444                                         self.owner,
445                                         rootdamage * coefficient * falloff,
446                                         WEP_ARC,
447                                         hitorigin,
448                                         WEP_CVAR(arc, beam_force) * new_dir * coefficient * falloff
449                                 );
450
451                                 new_beam_type = ARC_BT_HIT;
452                         }
453                         break; 
454                 }
455                 else if(trace_fraction != 1)
456                 {
457                         // we collided with geometry
458                         new_beam_type = ARC_BT_WALL;
459                         break;
460                 }
461                 else
462                 {
463                         last_origin = new_origin;
464                 }
465         }
466
467         // if we're bursting, use burst visual effects
468         new_beam_type += burst;
469
470         // network information: beam type
471         if(new_beam_type != self.beam_type)
472         {
473                 self.SendFlags |= ARC_SF_BEAMTYPE;
474                 self.beam_type = new_beam_type;
475         }
476
477         self.owner.beam_prev = time;
478         self.nextthink = time;
479 }
480
481 void W_Arc_Beam(float burst)
482 {
483         // only play fire sound if 1 sec has passed since player let go the fire button
484         if(time - self.beam_prev > 1)
485         {
486                 sound(self, CH_WEAPON_A, "weapons/lgbeam_fire.wav", VOL_BASE, ATTN_NORM);
487         }
488
489         entity beam = self.arc_beam = spawn();
490         beam.classname = "W_Arc_Beam";
491         beam.solid = SOLID_NOT;
492         beam.think = W_Arc_Beam_Think;
493         beam.owner = self;
494         beam.movetype = MOVETYPE_NONE;
495         beam.bot_dodge = TRUE;
496         beam.bot_dodgerating = WEP_CVAR(arc, beam_damage);
497         beam.beam_bursting = burst;
498         Net_LinkEntity(beam, FALSE, 0, W_Arc_Beam_Send);
499
500         entity oldself = self;
501         self = beam;
502         self.think();
503         self = oldself;
504 }
505
506 float W_Arc(float req)
507 {
508         switch(req)
509         {
510                 case WR_AIM:
511                 {
512                         if(WEP_CVAR(arc, beam_botaimspeed))
513                         {
514                                 self.BUTTON_ATCK = bot_aim(
515                                         WEP_CVAR(arc, beam_botaimspeed),
516                                         0,
517                                         WEP_CVAR(arc, beam_botaimlifetime),
518                                         FALSE
519                                 );
520                         }
521                         else
522                         {
523                                 self.BUTTON_ATCK = bot_aim(
524                                         1000000,
525                                         0,
526                                         0.001,
527                                         FALSE
528                                 );
529                         }
530                         return TRUE;
531                 }
532                 case WR_THINK:
533                 {
534                         #if 0
535                         if(self.arc_beam.beam_heat > threshold)
536                         {
537                                 stop the beam somehow
538                                 play overheat animation
539                         }
540                         #endif
541
542                         if(self.BUTTON_ATCK || self.BUTTON_ATCK2 || self.arc_beam.beam_bursting)
543                         {
544                                 if(self.BUTTON_ATCK_prev)
545                                 {
546                                         #if 0
547                                         if(self.animstate_startframe == self.anim_shoot_x && self.animstate_numframes == self.anim_shoot_y)
548                                                 weapon_thinkf(WFRAME_DONTCHANGE, autocvar_g_balance_arc_primary_animtime, w_ready);
549                                         else
550                                         #endif
551                                                 weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
552                                 }
553
554                                 if((!self.arc_beam) || wasfreed(self.arc_beam))
555                                 {
556                                         if(weapon_prepareattack(!!self.BUTTON_ATCK2, 0))
557                                         {
558                                                 W_Arc_Beam(!!self.BUTTON_ATCK2);
559                                                 
560                                                 if(!self.BUTTON_ATCK_prev)
561                                                 {
562                                                         weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
563                                                         self.BUTTON_ATCK_prev = 1;
564                                                 }
565                                         }
566                                 }
567                         } 
568                         else // todo
569                         {
570                                 if(self.BUTTON_ATCK_prev != 0)
571                                 {
572                                         weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
573                                         ATTACK_FINISHED(self) = time + WEP_CVAR(arc, beam_refire) * W_WeaponRateFactor();
574                                 }
575                                 self.BUTTON_ATCK_prev = 0;
576                         }
577
578                         #if 0
579                         if(self.BUTTON_ATCK2)
580                         if(weapon_prepareattack(1, autocvar_g_balance_arc_secondary_refire))
581                         {
582                                 W_Arc_Attack2();
583                                 self.arc_count = autocvar_g_balance_arc_secondary_count;
584                                 weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_arc_secondary_animtime, w_arc_checkattack);
585                                 self.arc_secondarytime = time + autocvar_g_balance_arc_secondary_refire2 * W_WeaponRateFactor();
586                         }
587                         #endif
588
589                         return TRUE;
590                 }
591                 case WR_INIT:
592                 {
593                         precache_model("models/weapons/g_arc.md3");
594                         precache_model("models/weapons/v_arc.md3");
595                         precache_model("models/weapons/h_arc.iqm");
596                         precache_sound("weapons/lgbeam_fire.wav");
597                         if(!arc_shotorigin[0])
598                         {
599                                 arc_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 1);
600                                 arc_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 2);
601                                 arc_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 3);
602                                 arc_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 4);
603                         }
604                         ARC_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP)
605                         return TRUE;
606                 }
607                 case WR_CHECKAMMO1:
608                 {
609                         return ((!WEP_CVAR(arc, beam_ammo)) || (self.WEP_AMMO(ARC) > 0));
610                 }
611                 case WR_CHECKAMMO2:
612                 {
613                         return ((!WEP_CVAR(arc, burst_ammo)) || (self.WEP_AMMO(ARC) > 0));
614                 }
615                 case WR_CONFIG:
616                 {
617                         ARC_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS)
618                         return TRUE;
619                 }
620                 case WR_KILLMESSAGE:
621                 {
622                         if(w_deathtype & HITTYPE_SECONDARY)
623                         {
624                                 return WEAPON_ELECTRO_MURDER_ORBS;
625                         }
626                         else
627                         {
628                                 if(w_deathtype & HITTYPE_BOUNCE)
629                                         return WEAPON_ELECTRO_MURDER_COMBO;
630                                 else
631                                         return WEAPON_ELECTRO_MURDER_BOLT;
632                         }
633                 }
634         }
635         return FALSE;
636 }
637 #endif
638 #ifdef CSQC
639
640 .vector beam_color;
641 .float beam_alpha;
642 .float beam_thickness;
643 .float beam_traileffect;
644 .float beam_hiteffect;
645 .float beam_hitlight[4]; // 0: radius, 123: rgb
646 .float beam_muzzleeffect;
647 .float beam_muzzlelight[4]; // 0: radius, 123: rgb
648 .string beam_image;
649
650 .entity beam_muzzleentity;
651
652 .float beam_degreespersegment;
653 .float beam_distancepersegment;
654 .float beam_usevieworigin;
655 .float beam_initialized;
656 .float beam_maxangle;
657 .float beam_range;
658 .float beam_returnspeed;
659 .float beam_tightness;
660 .vector beam_shotorigin;
661 .vector beam_dir;
662
663 entity Draw_ArcBeam_callback_entity;
664 vector Draw_ArcBeam_callback_new_dir;
665 float Draw_ArcBeam_callback_segmentdist;
666 float Draw_ArcBeam_callback_last_thickness;
667 vector Draw_ArcBeam_callback_last_top;
668 vector Draw_ArcBeam_callback_last_bottom;
669
670 void Draw_ArcBeam_callback(vector start, vector hit, vector end)
671 {
672         entity beam = Draw_ArcBeam_callback_entity;
673         vector transformed_view_org;
674         transformed_view_org = WarpZone_TransformOrigin(WarpZone_trace_transform, view_origin);
675
676         vector thickdir = normalize(cross(normalize(start - hit), transformed_view_org - start));
677
678         vector hitorigin;
679
680         // draw segment
681         #if 0
682         if(trace_fraction != 1)
683         {
684                 // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?)
685                 hitorigin = start + (Draw_ArcBeam_callback_new_dir * Draw_ArcBeam_callback_segmentdist * trace_fraction);
686                 hitorigin = WarpZone_TransformOrigin(WarpZone_trace_transform, hitorigin);
687         }
688         else
689         {
690                 hitorigin = hit;
691         }
692         #else
693         hitorigin = hit;
694         #endif
695
696         // decide upon thickness
697         float thickness = beam.beam_thickness;
698
699         // draw primary beam render
700         vector top    = hitorigin + (thickdir * thickness);
701         vector bottom = hitorigin - (thickdir * thickness);
702         //vector last_top    = start + (thickdir * Draw_ArcBeam_callback_last_thickness);
703         //vector last_bottom = start - (thickdir * Draw_ArcBeam_callback_last_thickness);
704
705         R_BeginPolygon(beam.beam_image, DRAWFLAG_NORMAL); // DRAWFLAG_ADDITIVE
706         R_PolygonVertex(
707                 top,
708                 '0 0.5 0' + ('0 0.5 0' * (thickness / beam.beam_thickness)),
709                 beam.beam_color,
710                 beam.beam_alpha
711         );
712         R_PolygonVertex(
713                 Draw_ArcBeam_callback_last_top,
714                 '0 0.5 0' + ('0 0.5 0' * (Draw_ArcBeam_callback_last_thickness / beam.beam_thickness)),
715                 beam.beam_color,
716                 beam.beam_alpha
717         );
718         R_PolygonVertex(
719                 Draw_ArcBeam_callback_last_bottom,
720                 '0 0.5 0' * (1 - (Draw_ArcBeam_callback_last_thickness / beam.beam_thickness)),
721                 beam.beam_color,
722                 beam.beam_alpha
723         );
724         R_PolygonVertex(
725                 bottom,
726                 '0 0.5 0' * (1 - (thickness / beam.beam_thickness)),
727                 beam.beam_color,
728                 beam.beam_alpha
729         );
730         R_EndPolygon();
731
732         // draw trailing particles
733         // NOTES:
734         //  - Don't use spammy particle counts here, use a FEW small particles around the beam
735         //  - We're not using WarpZone_TrailParticles here because we will handle warpzones ourselves.
736         if(beam.beam_traileffect)
737         {
738                 trailparticles(beam, beam.beam_traileffect, start, hitorigin);
739         }
740
741         // set up for the next 
742         Draw_ArcBeam_callback_last_thickness = thickness;
743         Draw_ArcBeam_callback_last_top = top;
744         Draw_ArcBeam_callback_last_bottom = bottom;
745 }
746
747 void Draw_ArcBeam(void)
748 {
749         if(!self.beam_usevieworigin)
750         {
751                 InterpolateOrigin_Do();
752         }
753
754         // origin = beam starting origin
755         // v_angle = wanted/aim direction
756         // angles = current direction of beam
757
758         vector start_pos;
759         vector wantdir; //= view_forward;
760         vector beamdir; //= self.beam_dir;
761
762         float segments;
763         if(self.beam_usevieworigin)
764         {
765                 // WEAPONTODO:
766                 // Currently we have to replicate nearly the same method of figuring
767                 // out the shotdir that the server does... Ideally in the future we
768                 // should be able to acquire this from a generalized function built
769                 // into a weapon system for client code. 
770
771                 // find where we are aiming
772                 makevectors(view_angles);
773
774                 // decide upon start position
775                 if(self.beam_usevieworigin == 2)
776                         { start_pos = view_origin; }
777                 else
778                         { start_pos = self.origin; }
779
780                 // trace forward with an estimation
781                 WarpZone_TraceLine(
782                         start_pos,
783                         start_pos + view_forward * self.beam_range,
784                         MOVE_NOMONSTERS,
785                         self
786                 );
787
788                 // untransform in case our trace went through a warpzone
789                 vector vf, vr, vu;
790                 vf = view_forward;
791                 vr = view_right;
792                 vu = view_up;
793                 vector end_pos = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos);
794                 view_forward = vf;
795                 view_right = vr;
796                 view_up = vu;
797
798                 // un-adjust trueaim if shotend is too close
799                 if(vlen(end_pos - view_origin) < g_trueaim_minrange)
800                         end_pos = view_origin + (view_forward * g_trueaim_minrange);
801
802                 // move shot origin to the actual gun muzzle origin
803                 vector origin_offset =
804                         view_forward * self.beam_shotorigin_x
805                         + view_right * -self.beam_shotorigin_y 
806                         + view_up * self.beam_shotorigin_z;
807
808                 start_pos = start_pos + origin_offset;
809
810                 // calculate the aim direction now
811                 wantdir = normalize(end_pos - start_pos);
812
813                 if(!self.beam_initialized)
814                 {
815                         self.beam_dir = wantdir;
816                         self.beam_initialized = TRUE;
817                 }
818
819                 if(self.beam_dir != wantdir)
820                 {
821                         // calculate how much we're going to move the end of the beam to the want position
822                         // WEAPONTODO (server and client):
823                         // blendfactor never actually becomes 0 in this situation, which is a problem
824                         // regarding precision... this means that self.beam_dir and w_shotdir approach
825                         // eachother, however they never actually become the same value with this method.
826                         // Perhaps we should do some form of rounding/snapping?
827                         float angle = vlen(wantdir - self.beam_dir) * RAD2DEG;
828                         if(angle && (angle > self.beam_maxangle))
829                         {
830                                 // if the angle is greater than maxangle, force the blendfactor to make this the maximum factor
831                                 float blendfactor = bound(
832                                         0,
833                                         (1 - (self.beam_returnspeed * frametime)),
834                                         min(self.beam_maxangle / angle, 1)
835                                 );
836                                 self.beam_dir = normalize((wantdir * (1 - blendfactor)) + (self.beam_dir * blendfactor));
837                         }
838                         else
839                         {
840                                 // the radius is not too far yet, no worries :D
841                                 float blendfactor = bound(
842                                         0,
843                                         (1 - (self.beam_returnspeed * frametime)),
844                                         1
845                                 );
846                                 self.beam_dir = normalize((wantdir * (1 - blendfactor)) + (self.beam_dir * blendfactor));
847                         }
848
849                         // calculate how many segments are needed
850                         float max_allowed_segments;
851
852                         if(self.beam_distancepersegment)
853                         {
854                                 max_allowed_segments = min(
855                                         ARC_MAX_SEGMENTS,
856                                         1 + (vlen(wantdir / self.beam_distancepersegment))
857                                 );
858                         }
859                         else { max_allowed_segments = ARC_MAX_SEGMENTS; }
860
861                         if(self.beam_degreespersegment)
862                         {
863                                 segments = bound(
864                                         1, 
865                                         (
866                                                 min(
867                                                         angle,
868                                                         self.beam_maxangle
869                                                 )
870                                                 /
871                                                 self.beam_degreespersegment
872                                         ),
873                                         max_allowed_segments
874                                 );
875                         }
876                         else { segments = 1; }
877                 }
878                 else { segments = 1; }
879
880                 // set the beam direction which the rest of the code will refer to
881                 beamdir = self.beam_dir;
882
883                 // finally, set self.angles to the proper direction so that muzzle attachment points in proper direction
884                 self.angles = fixedvectoangles2(view_forward, view_up);
885         }
886         else
887         {
888                 // set the values from the provided info from the networked entity
889                 start_pos = self.origin;
890                 wantdir = self.v_angle;
891                 beamdir = self.angles;
892
893                 if(beamdir != wantdir)
894                 {
895                         float angle = vlen(wantdir - beamdir) * RAD2DEG;
896
897                         // calculate how many segments are needed
898                         float max_allowed_segments;
899
900                         if(self.beam_distancepersegment)
901                         {
902                                 max_allowed_segments = min(
903                                         ARC_MAX_SEGMENTS,
904                                         1 + (vlen(wantdir / self.beam_distancepersegment))
905                                 );
906                         }
907                         else { max_allowed_segments = ARC_MAX_SEGMENTS; }
908
909                         if(self.beam_degreespersegment)
910                         {
911                                 segments = bound(
912                                         1, 
913                                         (
914                                                 min(
915                                                         angle,
916                                                         self.beam_maxangle
917                                                 )
918                                                 /
919                                                 self.beam_degreespersegment
920                                         ),
921                                         max_allowed_segments
922                                 );
923                         }
924                         else { segments = 1; }
925                 }
926                 else { segments = 1; }
927         }
928
929         setorigin(self, start_pos);
930         self.beam_muzzleentity.angles_z = random() * 360; // WEAPONTODO: use avelocity instead?
931
932         vector beam_endpos_estimate = (start_pos + (beamdir * self.beam_range));
933
934         Draw_ArcBeam_callback_entity = self;
935         Draw_ArcBeam_callback_last_thickness = 0;
936         Draw_ArcBeam_callback_last_top = start_pos;
937         Draw_ArcBeam_callback_last_bottom = start_pos;
938
939         vector last_origin = start_pos;
940
941         float i;
942         for(i = 1; i <= segments; ++i)
943         {
944                 // WEAPONTODO (server and client):
945                 // Segment blend and distance should probably really be calculated in a better way,
946                 // however I am not sure how to do it properly. There are a few things I have tried,
947                 // but most of them do not work properly due to my lack of understanding regarding
948                 // the mathematics behind them.
949
950                 // Ideally, we should calculate the positions along a perfect curve
951                 // between wantdir and self.beam_dir with an option for depth of arc
952
953                 // Another issue is that (on the client code) we must separate the
954                 // curve into multiple rendered curves when handling warpzones.
955
956                 // I can handle this by detecting it for each segment, however that
957                 // is a fairly inefficient method in comparison to having a curved line
958                 // drawing function similar to Draw_CylindricLine that accepts
959                 // top and bottom origins as input, this way there would be no
960                 // overlapping edges when connecting the curved pieces.
961
962                 // WEAPONTODO (client):
963                 // In order to do nice fading and pointing on the starting segment, we must always
964                 // have that drawn as a separate triangle... However, that is difficult to do when
965                 // keeping in mind the above problems and also optimizing the amount of segments
966                 // drawn on screen at any given time. (Automatic beam quality scaling, essentially)
967
968                 // calculate this on every segment to ensure that we always reach the full length of the attack
969                 float segmentblend = bound(0, (i/segments) + self.beam_tightness, 1);
970                 float segmentdist = vlen(beam_endpos_estimate - last_origin) * (i/segments);
971
972                 // WEAPONTODO: Apparently, normalize is not the correct function to use here...
973                 // Figure out how this actually should work.
974                 vector new_dir = normalize(
975                         (wantdir * (1 - segmentblend))
976                         +
977                         (normalize(beam_endpos_estimate - last_origin) * segmentblend)
978                 );
979                 vector new_origin = last_origin + (new_dir * segmentdist);
980
981                 Draw_ArcBeam_callback_segmentdist = segmentdist;
982                 Draw_ArcBeam_callback_new_dir = new_dir;
983
984                 WarpZone_TraceBox_ThroughZone(
985                         last_origin,
986                         '0 0 0',
987                         '0 0 0',
988                         new_origin,
989                         MOVE_NORMAL,
990                         world,
991                         world,
992                         Draw_ArcBeam_callback
993                 );
994
995                 //printf("segment: %d, warpzone transform: %d\n", i, (WarpZone_trace_transform != world));
996
997                 // WEAPONTODO:
998                 // Figure out some way to detect a collision with geometry with callback...
999                 // That way we can know when we are done drawing the beam and skip
1000                 // the rest of the segments without breaking warpzone support.
1001
1002                 last_origin = WarpZone_TransformOrigin(WarpZone_trace_transform, new_origin);
1003                 beam_endpos_estimate = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_endpos_estimate);
1004         }
1005
1006         // startpoint and endpoint drawn visual effects
1007         if(self.beam_hiteffect)
1008         {
1009                 pointparticles(
1010                         self.beam_hiteffect,
1011                         last_origin,
1012                         beamdir * -1,
1013                         frametime * 2
1014                 );
1015         }
1016         if(self.beam_hitlight[0])
1017         {
1018                 adddynamiclight(
1019                         last_origin,
1020                         self.beam_hitlight[0],
1021                         vec3(
1022                                 self.beam_hitlight[1],
1023                                 self.beam_hitlight[2],
1024                                 self.beam_hitlight[3]
1025                         )
1026                 );
1027         }
1028         if(self.beam_muzzleeffect)
1029         {
1030                 pointparticles(
1031                         self.beam_muzzleeffect,
1032                         start_pos + wantdir * 20,
1033                         wantdir * 1000,
1034                         frametime * 0.1
1035                 );
1036         }
1037         if(self.beam_muzzlelight[0])
1038         {
1039                 adddynamiclight(
1040                         start_pos + wantdir * 20,
1041                         self.beam_muzzlelight[0],
1042                         vec3(
1043                                 self.beam_muzzlelight[1],
1044                                 self.beam_muzzlelight[2],
1045                                 self.beam_muzzlelight[3]
1046                         )
1047                 );
1048         }
1049
1050         // cleanup
1051         Draw_ArcBeam_callback_entity = world;
1052         Draw_ArcBeam_callback_new_dir = '0 0 0';
1053         Draw_ArcBeam_callback_segmentdist = 0;
1054         Draw_ArcBeam_callback_last_thickness = 0;
1055         Draw_ArcBeam_callback_last_top = '0 0 0';
1056         Draw_ArcBeam_callback_last_bottom = '0 0 0';
1057 }
1058
1059 void Remove_ArcBeam(void)
1060 {
1061         remove(self.beam_muzzleentity);
1062         sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTEN_NORM);
1063 }
1064
1065 void Ent_ReadArcBeam(float isnew)
1066 {
1067         float sf = ReadByte();
1068         entity flash;
1069
1070         if(isnew)
1071         {
1072                 // calculate shot origin offset from gun alignment
1073                 float gunalign = autocvar_cl_gunalign;
1074                 if(gunalign != 1 && gunalign != 2 && gunalign != 4)
1075                         gunalign = 3; // default value
1076                 --gunalign;
1077
1078                 self.beam_shotorigin = arc_shotorigin[gunalign];
1079
1080                 // set other main attributes of the beam
1081                 self.draw = Draw_ArcBeam;
1082                 self.entremove = Remove_ArcBeam;
1083                 sound(self, CH_SHOTS_SINGLE, "weapons/lgbeam_fly.wav", VOL_BASE, ATTEN_NORM);
1084
1085                 flash = spawn();
1086                 flash.owner = self;
1087                 flash.effects = EF_ADDITIVE | EF_FULLBRIGHT;
1088                 flash.drawmask = MASK_NORMAL;
1089                 flash.solid = SOLID_NOT;
1090                 setattachment(flash, self, "");
1091                 setorigin(flash, '0 0 0');
1092
1093                 self.beam_muzzleentity = flash;
1094         }
1095         else
1096         {
1097                 flash = self.beam_muzzleentity;
1098         }
1099
1100         if(sf & ARC_SF_SETTINGS) // settings information
1101         {
1102                 self.beam_degreespersegment = ReadShort();
1103                 self.beam_distancepersegment = ReadShort();
1104                 self.beam_maxangle = ReadShort();
1105                 self.beam_range = ReadCoord();
1106                 self.beam_returnspeed = ReadShort();
1107                 self.beam_tightness = (ReadByte() / 10);
1108
1109                 if(ReadByte())
1110                 {
1111                         if(autocvar_chase_active)
1112                                 { self.beam_usevieworigin = 1; }
1113                         else // use view origin
1114                                 { self.beam_usevieworigin = 2; }
1115                 }
1116                 else
1117                 {
1118                         self.beam_usevieworigin = 0;
1119                 }
1120         }
1121
1122         if(!self.beam_usevieworigin)
1123         {
1124                 // self.iflags = IFLAG_ORIGIN | IFLAG_ANGLES | IFLAG_V_ANGLE; // why doesn't this work?
1125                 self.iflags = IFLAG_ORIGIN;
1126
1127                 InterpolateOrigin_Undo();
1128         }
1129
1130         if(sf & ARC_SF_START) // starting location
1131         {
1132                 self.origin_x = ReadCoord();
1133                 self.origin_y = ReadCoord();
1134                 self.origin_z = ReadCoord();
1135         }
1136         else if(self.beam_usevieworigin) // infer the location from player location
1137         {
1138                 if(self.beam_usevieworigin == 2)
1139                 {
1140                         // use view origin
1141                         self.origin = view_origin;
1142                 }
1143                 else
1144                 {
1145                         // use player origin so that third person display still works
1146                         self.origin = getplayerorigin(player_localnum) + ('0 0 1' * getstati(STAT_VIEWHEIGHT));
1147                 }
1148         }
1149
1150         setorigin(self, self.origin);
1151
1152         if(sf & ARC_SF_WANTDIR) // want/aim direction
1153         {
1154                 self.v_angle_x = ReadCoord();
1155                 self.v_angle_y = ReadCoord();
1156                 self.v_angle_z = ReadCoord();
1157         }
1158
1159         if(sf & ARC_SF_BEAMDIR) // beam direction
1160         {
1161                 self.angles_x = ReadCoord();
1162                 self.angles_y = ReadCoord();
1163                 self.angles_z = ReadCoord();
1164         }
1165
1166         if(sf & ARC_SF_BEAMTYPE) // beam type
1167         {
1168                 self.beam_type = ReadByte();
1169                 switch(self.beam_type)
1170                 {
1171                         case ARC_BT_MISS:
1172                         {
1173                                 self.beam_color = '-1 -1 1';
1174                                 self.beam_alpha = 0.5;
1175                                 self.beam_thickness = 8;
1176                                 self.beam_traileffect = FALSE;
1177                                 self.beam_hiteffect = particleeffectnum("electro_lightning");
1178                                 self.beam_hitlight[0] = 0;
1179                                 self.beam_hitlight[1] = 1;
1180                                 self.beam_hitlight[2] = 1;
1181                                 self.beam_hitlight[3] = 1;
1182                                 self.beam_muzzleeffect = FALSE; //particleeffectnum("nex_muzzleflash");
1183                                 self.beam_muzzlelight[0] = 0;
1184                                 self.beam_muzzlelight[1] = 1;
1185                                 self.beam_muzzlelight[2] = 1;
1186                                 self.beam_muzzlelight[3] = 1;
1187                                 self.beam_image = "particles/lgbeam";
1188                                 setmodel(flash, "models/flash.md3");
1189                                 flash.alpha = self.beam_alpha;
1190                                 flash.colormod = self.beam_color;
1191                                 flash.scale = 0.5;
1192                                 break;
1193                         }
1194                         case ARC_BT_WALL: // grenadelauncher_muzzleflash healray_muzzleflash
1195                         {
1196                                 self.beam_color = '0.5 0.5 1';
1197                                 self.beam_alpha = 0.5;
1198                                 self.beam_thickness = 8;
1199                                 self.beam_traileffect = FALSE;
1200                                 self.beam_hiteffect = particleeffectnum("electro_lightning");
1201                                 self.beam_hitlight[0] = 0;
1202                                 self.beam_hitlight[1] = 1;
1203                                 self.beam_hitlight[2] = 1;
1204                                 self.beam_hitlight[3] = 1;
1205                                 self.beam_muzzleeffect = FALSE; // particleeffectnum("grenadelauncher_muzzleflash");
1206                                 self.beam_muzzlelight[0] = 0;
1207                                 self.beam_muzzlelight[1] = 1;
1208                                 self.beam_muzzlelight[2] = 1;
1209                                 self.beam_muzzlelight[3] = 1;
1210                                 self.beam_image = "particles/lgbeam";
1211                                 setmodel(flash, "models/flash.md3");
1212                                 flash.alpha = self.beam_alpha;
1213                                 flash.colormod = self.beam_color;
1214                                 flash.scale = 0.5;
1215                                 break;
1216                         }
1217                         case ARC_BT_HEAL:
1218                         {
1219                                 self.beam_color = '0 1 0';
1220                                 self.beam_alpha = 0.5;
1221                                 self.beam_thickness = 8;
1222                                 self.beam_traileffect = FALSE;
1223                                 self.beam_hiteffect = particleeffectnum("healray_impact"); 
1224                                 self.beam_hitlight[0] = 0;
1225                                 self.beam_hitlight[1] = 1;
1226                                 self.beam_hitlight[2] = 1;
1227                                 self.beam_hitlight[3] = 1;
1228                                 self.beam_muzzleeffect = FALSE; //particleeffectnum("nex_muzzleflash");
1229                                 self.beam_muzzlelight[0] = 0;
1230                                 self.beam_muzzlelight[1] = 1;
1231                                 self.beam_muzzlelight[2] = 1;
1232                                 self.beam_muzzlelight[3] = 1;
1233                                 self.beam_image = "particles/lgbeam";
1234                                 setmodel(flash, "models/flash.md3");
1235                                 flash.alpha = self.beam_alpha;
1236                                 flash.colormod = self.beam_color;
1237                                 flash.scale = 0.5;
1238                                 break;
1239                         }
1240                         case ARC_BT_HIT:
1241                         {
1242                                 self.beam_color = '1 0 1';
1243                                 self.beam_alpha = 0.5;
1244                                 self.beam_thickness = 8;
1245                                 self.beam_traileffect = particleeffectnum("nex_beam");
1246                                 self.beam_hiteffect = particleeffectnum("electro_lightning"); 
1247                                 self.beam_hitlight[0] = 20;
1248                                 self.beam_hitlight[1] = 1;
1249                                 self.beam_hitlight[2] = 0;
1250                                 self.beam_hitlight[3] = 0;
1251                                 self.beam_muzzleeffect = FALSE; //particleeffectnum("nex_muzzleflash");
1252                                 self.beam_muzzlelight[0] = 50;
1253                                 self.beam_muzzlelight[1] = 1;
1254                                 self.beam_muzzlelight[2] = 0;
1255                                 self.beam_muzzlelight[3] = 0;
1256                                 self.beam_image = "particles/lgbeam";
1257                                 setmodel(flash, "models/flash.md3");
1258                                 flash.alpha = self.beam_alpha;
1259                                 flash.colormod = self.beam_color;
1260                                 flash.scale = 0.5;
1261                                 break;
1262                         }
1263                         case ARC_BT_BURST_MISS:
1264                         {
1265                                 self.beam_color = '-1 -1 1';
1266                                 self.beam_alpha = 0.5;
1267                                 self.beam_thickness = 14;
1268                                 self.beam_traileffect = FALSE;
1269                                 self.beam_hiteffect = particleeffectnum("electro_lightning"); 
1270                                 self.beam_hitlight[0] = 0;
1271                                 self.beam_hitlight[1] = 1;
1272                                 self.beam_hitlight[2] = 1;
1273                                 self.beam_hitlight[3] = 1;
1274                                 self.beam_muzzleeffect = FALSE; //particleeffectnum("nex_muzzleflash");
1275                                 self.beam_muzzlelight[0] = 0;
1276                                 self.beam_muzzlelight[1] = 1;
1277                                 self.beam_muzzlelight[2] = 1;
1278                                 self.beam_muzzlelight[3] = 1;
1279                                 self.beam_image = "particles/lgbeam";
1280                                 setmodel(flash, "models/flash.md3");
1281                                 flash.alpha = self.beam_alpha;
1282                                 flash.colormod = self.beam_color;
1283                                 flash.scale = 0.5;
1284                                 break;
1285                         }
1286                         case ARC_BT_BURST_WALL:
1287                         {
1288                                 self.beam_color = '0.5 0.5 1';
1289                                 self.beam_alpha = 0.5;
1290                                 self.beam_thickness = 14;
1291                                 self.beam_traileffect = FALSE;
1292                                 self.beam_hiteffect = particleeffectnum("electro_lightning"); 
1293                                 self.beam_hitlight[0] = 0;
1294                                 self.beam_hitlight[1] = 1;
1295                                 self.beam_hitlight[2] = 1;
1296                                 self.beam_hitlight[3] = 1;
1297                                 self.beam_muzzleeffect = FALSE; //particleeffectnum("nex_muzzleflash");
1298                                 self.beam_muzzlelight[0] = 0;
1299                                 self.beam_muzzlelight[1] = 1;
1300                                 self.beam_muzzlelight[2] = 1;
1301                                 self.beam_muzzlelight[3] = 1;
1302                                 self.beam_image = "particles/lgbeam";
1303                                 setmodel(flash, "models/flash.md3");
1304                                 flash.alpha = self.beam_alpha;
1305                                 flash.colormod = self.beam_color;
1306                                 flash.scale = 0.5;
1307                                 break;
1308                         }
1309                         case ARC_BT_BURST_HEAL:
1310                         {
1311                                 self.beam_color = '0 1 0';
1312                                 self.beam_alpha = 0.5;
1313                                 self.beam_thickness = 14;
1314                                 self.beam_traileffect = FALSE;
1315                                 self.beam_hiteffect = particleeffectnum("electro_lightning"); 
1316                                 self.beam_hitlight[0] = 0;
1317                                 self.beam_hitlight[1] = 1;
1318                                 self.beam_hitlight[2] = 1;
1319                                 self.beam_hitlight[3] = 1;
1320                                 self.beam_muzzleeffect = FALSE; //particleeffectnum("nex_muzzleflash");
1321                                 self.beam_muzzlelight[0] = 0;
1322                                 self.beam_muzzlelight[1] = 1;
1323                                 self.beam_muzzlelight[2] = 1;
1324                                 self.beam_muzzlelight[3] = 1;
1325                                 self.beam_image = "particles/lgbeam";
1326                                 setmodel(flash, "models/flash.md3");
1327                                 flash.alpha = self.beam_alpha;
1328                                 flash.colormod = self.beam_color;
1329                                 flash.scale = 0.5;
1330                                 break;
1331                         }
1332                         case ARC_BT_BURST_HIT:
1333                         {
1334                                 self.beam_color = '1 0 1';
1335                                 self.beam_alpha = 0.5;
1336                                 self.beam_thickness = 14;
1337                                 self.beam_traileffect = FALSE;
1338                                 self.beam_hiteffect = particleeffectnum("electro_lightning"); 
1339                                 self.beam_hitlight[0] = 0;
1340                                 self.beam_hitlight[1] = 1;
1341                                 self.beam_hitlight[2] = 1;
1342                                 self.beam_hitlight[3] = 1;
1343                                 self.beam_muzzleeffect = FALSE; //particleeffectnum("nex_muzzleflash");
1344                                 self.beam_muzzlelight[0] = 0;
1345                                 self.beam_muzzlelight[1] = 1;
1346                                 self.beam_muzzlelight[2] = 1;
1347                                 self.beam_muzzlelight[3] = 1;
1348                                 self.beam_image = "particles/lgbeam";
1349                                 setmodel(flash, "models/flash.md3");
1350                                 flash.alpha = self.beam_alpha;
1351                                 flash.colormod = self.beam_color;
1352                                 flash.scale = 0.5;
1353                                 break;
1354                         }
1355
1356                         // shouldn't be possible, but lets make it colorful if it does :D
1357                         default:
1358                         {
1359                                 self.beam_color = randomvec();
1360                                 self.beam_alpha = 1;
1361                                 self.beam_thickness = 8;
1362                                 self.beam_traileffect = FALSE;
1363                                 self.beam_hiteffect = FALSE; 
1364                                 self.beam_hitlight[0] = 0;
1365                                 self.beam_hitlight[1] = 1;
1366                                 self.beam_hitlight[2] = 1;
1367                                 self.beam_hitlight[3] = 1;
1368                                 self.beam_muzzleeffect = FALSE; //particleeffectnum("nex_muzzleflash");
1369                                 self.beam_muzzlelight[0] = 0;
1370                                 self.beam_muzzlelight[1] = 1;
1371                                 self.beam_muzzlelight[2] = 1;
1372                                 self.beam_muzzlelight[3] = 1;
1373                                 self.beam_image = "particles/lgbeam";
1374                                 setmodel(flash, "models/flash.md3");
1375                                 flash.alpha = self.beam_alpha;
1376                                 flash.colormod = self.beam_color;
1377                                 flash.scale = 0.5;
1378                                 break;
1379                         }
1380                 }
1381         }
1382
1383         if(!self.beam_usevieworigin)
1384         {
1385                 InterpolateOrigin_Note();
1386         }
1387 }
1388
1389 float W_Arc(float req)
1390 {
1391         switch(req)
1392         {
1393                 case WR_IMPACTEFFECT:
1394                 {
1395                         // todo
1396                         return TRUE;
1397                 }
1398                 case WR_INIT:
1399                 {
1400                         //precache_sound("weapons/arc_impact.wav");
1401                         //precache_sound("weapons/arc_impact_combo.wav");
1402                         return TRUE;
1403                 }
1404                 case WR_ZOOMRETICLE:
1405                 {
1406                         // no weapon specific image for this weapon
1407                         return FALSE;
1408                 }
1409         }
1410         return FALSE;
1411 }
1412 #endif
1413 #endif