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