]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/weapons/w_arc.qc
Fix some various issues with angle blending
[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 vector arc_shotorigin[4];
54 .vector beam_start;
55 .vector beam_dir;
56 .vector beam_wantdir;
57 .float beam_type;
58 #define ARC_BT_MISS        0
59 #define ARC_BT_WALL        1
60 #define ARC_BT_HEAL        2
61 #define ARC_BT_HIT         3
62 #define ARC_BT_BURST_MISS  10
63 #define ARC_BT_BURST_WALL  11
64 #define ARC_BT_BURST_HEAL  12
65 #define ARC_BT_BURST_HIT   13
66 #define ARC_BT_BURSTMASK   10
67 #endif
68 #ifdef SVQC
69 #define ARC_MAX_SEGMENTS 20
70 ARC_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
71 .entity arc_beam;
72 .float BUTTON_ATCK_prev; // for better animation control
73 .float beam_prev;
74 .float beam_initialized;
75 .float beam_bursting;
76 #endif
77 #else
78 #ifdef SVQC
79 void spawnfunc_weapon_arc(void) { weapon_defaultspawnfunc(WEP_ARC); }
80
81 float W_Arc_Beam_Send(entity to, float sf)
82 {
83         WriteByte(MSG_ENTITY, ENT_CLIENT_ARC_BEAM);
84
85         // Truncate information when this beam is displayed to the owner client
86         // - The owner client has no use for beam start position or directions,
87         //    it always figures this information out for itself with csqc code.
88         // - Spectating the owner also truncates this information.
89         float drawlocal = ((to == self.owner) || ((to.enemy == self.owner) && IS_SPEC(to)));
90         if(drawlocal)
91         {
92                 #if 0
93                 sf &= ~2;
94                 sf &= ~4;
95                 sf &= ~8;
96                 #else
97                 sf &= ~14;
98                 #endif
99         }
100
101         WriteByte(MSG_ENTITY, sf);
102
103         if(sf & 1) // settings information
104         {
105                 WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_maxangle));
106                 WriteCoord(MSG_ENTITY, WEP_CVAR(arc, beam_range));
107                 WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_returnspeed));
108                 WriteByte(MSG_ENTITY, WEP_CVAR(arc, beam_tightness) * 10);
109
110                 WriteByte(MSG_ENTITY, drawlocal);
111         }
112         if(sf & 2) // starting location
113         {
114                 WriteCoord(MSG_ENTITY, self.beam_start_x);
115                 WriteCoord(MSG_ENTITY, self.beam_start_y);
116                 WriteCoord(MSG_ENTITY, self.beam_start_z);
117         }
118         if(sf & 4) // want/aim direction
119         {
120                 WriteCoord(MSG_ENTITY, self.beam_wantdir_x);
121                 WriteCoord(MSG_ENTITY, self.beam_wantdir_y);
122                 WriteCoord(MSG_ENTITY, self.beam_wantdir_z);
123         }
124         if(sf & 8) // beam direction
125         {
126                 WriteCoord(MSG_ENTITY, self.beam_dir_x);
127                 WriteCoord(MSG_ENTITY, self.beam_dir_y);
128                 WriteCoord(MSG_ENTITY, self.beam_dir_z);
129         }
130         if(sf & 16) // beam type
131         {
132                 WriteByte(MSG_ENTITY, self.beam_type);
133         }
134
135         return TRUE;
136 }
137
138 void W_Arc_Beam_Think(void)
139 {
140         if(self != self.owner.arc_beam)
141         {
142                 remove(self);
143                 return;
144         }
145
146         if(
147                 (self.owner.WEP_AMMO(ARC) <= 0 && !(self.owner.items & IT_UNLIMITED_WEAPON_AMMO))
148                 ||
149                 self.owner.deadflag != DEAD_NO
150                 ||
151                 (!self.owner.BUTTON_ATCK && !self.beam_bursting)
152                 ||
153                 self.owner.freezetag_frozen
154         )
155         {
156                 if(self == self.owner.arc_beam) { self.owner.arc_beam = world; } // is this needed? I thought this is changed to world when removed ANYWAY
157                 entity oldself = self;
158                 self = self.owner;
159                 if(!WEP_ACTION(WEP_ARC, WR_CHECKAMMO1) && !WEP_ACTION(WEP_ARC, WR_CHECKAMMO2))
160                 {
161                         // note: this doesn't force the switch
162                         W_SwitchToOtherWeapon(self);
163                 }
164                 self = oldself;
165                 remove(self);
166                 return;
167         }
168
169         float burst = 0;
170         if(self.owner.BUTTON_ATCK2 || self.beam_bursting)
171         {
172                 if(!self.beam_bursting)
173                         self.beam_bursting = TRUE;
174                 burst = ARC_BT_BURSTMASK;
175         }
176
177         // decrease ammo
178         float coefficient = frametime;
179         if(!(self.owner.items & IT_UNLIMITED_WEAPON_AMMO))
180         {
181                 float rootammo;
182                 if(burst)
183                         { rootammo = WEP_CVAR(arc, burst_ammo); }
184                 else
185                         { rootammo = WEP_CVAR(arc, beam_ammo); }
186
187                 if(rootammo)
188                 {
189                         coefficient = min(coefficient, self.owner.WEP_AMMO(ARC) / rootammo);
190                         self.owner.WEP_AMMO(ARC) = max(0, self.owner.WEP_AMMO(ARC) - (rootammo * frametime));
191                 }
192         }
193
194         makevectors(self.owner.v_angle);
195
196         W_SetupShot_Range(self.owner, TRUE, 0, "", 0, WEP_CVAR(arc, beam_damage) * coefficient, WEP_CVAR(arc, beam_range));
197
198         // network information: shot origin and want/aim direction
199         if(self.beam_start != w_shotorg)
200         {
201                 self.SendFlags |= 2;
202                 self.beam_start = w_shotorg;
203         }
204         if(self.beam_wantdir != w_shotdir)
205         {
206                 self.SendFlags |= 4;
207                 self.beam_wantdir = w_shotdir;
208         }
209
210         if(!self.beam_initialized)
211         {
212                 #ifdef ARC_DEBUG
213                 for(i = 0; i < ARC_MAX_SEGMENTS; ++i)
214                         self.lg_ents[i] = spawn();
215                 #endif
216                 
217                 self.beam_dir = w_shotdir;
218                 self.beam_initialized = TRUE;
219         }
220
221         // WEAPONTODO: Detect player velocity so that the beam curves when moving too
222         // idea: blend together self.beam_dir with the inverted direction the player is moving in
223         // might have to make some special accomodation so that it only uses view_right and view_up
224
225         // note that if we do this, it'll always be corrected to a maximum angle by beam_maxangle handling
226
227         float segments; 
228         if(self.beam_dir != w_shotdir)
229         {
230                 // calculate how much we're going to move the end of the beam to the want position
231                 // WEAPONTODO (server and client):
232                 // blendfactor never actually becomes 0 in this situation, which is a problem
233                 // regarding precision... this means that self.beam_dir and w_shotdir approach
234                 // eachother, however they never actually become the same value with this method.
235                 // Perhaps we should do some form of rounding/snapping?
236                 float angle = vlen(w_shotdir - self.beam_dir) * RAD2DEG;
237                 if(angle && (angle > WEP_CVAR(arc, beam_maxangle)))
238                 {
239                         // if the angle is greater than maxangle, force the blendfactor to make this the maximum factor
240                         float blendfactor = bound(
241                                 0,
242                                 (1 - (WEP_CVAR(arc, beam_returnspeed) * frametime)),
243                                 min(WEP_CVAR(arc, beam_maxangle) / angle, 1)
244                         );
245                         self.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (self.beam_dir * blendfactor));
246                 }
247                 else
248                 {
249                         // the radius is not too far yet, no worries :D
250                         float blendfactor = bound(
251                                 0,
252                                 (1 - (WEP_CVAR(arc, beam_returnspeed) * frametime)),
253                                 1
254                         );
255                         self.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (self.beam_dir * blendfactor));
256                 }
257
258                 // network information: beam direction
259                 self.SendFlags |= 8;
260
261                 // calculate how many segments are needed
262                 float max_allowed_segments;
263
264                 if(WEP_CVAR(arc, beam_distancepersegment))
265                 {
266                         max_allowed_segments = min(
267                                 ARC_MAX_SEGMENTS,
268                                 1 + (vlen(w_shotdir / WEP_CVAR(arc, beam_distancepersegment)))
269                         );
270                 }
271                 else { max_allowed_segments = ARC_MAX_SEGMENTS; }
272
273                 if(WEP_CVAR(arc, beam_degreespersegment))
274                 {
275                         segments = min(
276                                 max(
277                                         1,
278                                         (
279                                                 min(
280                                                         angle,
281                                                         WEP_CVAR(arc, beam_maxangle)
282                                                 )
283                                                 /
284                                                 WEP_CVAR(arc, beam_degreespersegment)
285                                         )
286                                 ),
287                                 max_allowed_segments
288                         );
289                 }
290                 else { segments = 1; }
291         }
292         else { segments = 1; }
293
294         vector beam_endpos_estimate = (w_shotorg + (self.beam_dir * WEP_CVAR(arc, beam_range)));
295
296         float i;
297         float new_beam_type = 0;
298         vector last_origin = w_shotorg;
299         for(i = 1; i <= segments; ++i)
300         {
301                 // WEAPONTODO (server and client):
302                 // Segment blend and distance should probably really be calculated in a better way,
303                 // however I am not sure how to do it properly. There are a few things I have tried,
304                 // but most of them do not work properly due to my lack of understanding regarding
305                 // the mathematics behind them.
306
307                 // Ideally, we should calculate the positions along a perfect curve
308                 // between wantdir and self.beam_dir with an option for depth of arc
309
310                 // Another issue is that (on the client code) we must separate the
311                 // curve into multiple rendered curves when handling warpzones.
312                 
313                 // I can handle this by detecting it for each segment, however that
314                 // is a fairly inefficient method in comparison to having a curved line
315                 // drawing function similar to Draw_CylindricLine that accepts
316                 // top and bottom origins as input, this way there would be no
317                 // overlapping edges when connecting the curved pieces.
318
319                 // WEAPONTODO (client):
320                 // In order to do nice fading and pointing on the starting segment, we must always
321                 // have that drawn as a separate triangle... However, that is difficult to do when
322                 // keeping in mind the above problems and also optimizing the amount of segments
323                 // drawn on screen at any given time. (Automatic beam quality scaling, essentially)
324
325                 // calculate this on every segment to ensure that we always reach the full length of the attack
326                 float segmentblend = bound(0, (i/segments) + WEP_CVAR(arc, beam_tightness), 1);
327                 float segmentdist = vlen(beam_endpos_estimate - last_origin) * (i/segments);
328
329                 // WEAPONTODO: Apparently, normalize is not the correct function to use here...
330                 // Figure out how this actually should work.
331                 vector new_dir = normalize(
332                         (w_shotdir * (1 - segmentblend))
333                         +
334                         (normalize(beam_endpos_estimate - last_origin) * segmentblend)
335                 );
336                 vector new_origin = last_origin + (new_dir * segmentdist);
337
338                 WarpZone_traceline_antilag(
339                         self.owner,
340                         last_origin,
341                         new_origin,
342                         MOVE_NORMAL,
343                         self.owner,
344                         ANTILAG_LATENCY(self.owner)
345                 );
346
347                 float is_player = (
348                         trace_ent.classname == "player"
349                         ||
350                         trace_ent.classname == "body"
351                         ||
352                         (trace_ent.flags & FL_MONSTER)
353                 );
354
355                 if(trace_ent && trace_ent.takedamage && (is_player || WEP_CVAR(arc, beam_nonplayerdamage)))
356                 {
357                         // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?)
358                         vector hitorigin = last_origin + (new_dir * segmentdist * trace_fraction);
359
360                         float falloff = ExponentialFalloff(
361                                 WEP_CVAR(arc, beam_falloff_mindist),
362                                 WEP_CVAR(arc, beam_falloff_maxdist),
363                                 WEP_CVAR(arc, beam_falloff_halflifedist),
364                                 vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, hitorigin) - w_shotorg)
365                         );
366
367                         if(is_player && SAME_TEAM(self.owner, trace_ent))
368                         {
369                                 float roothealth, rootarmor;
370                                 if(burst)
371                                 {
372                                         roothealth = WEP_CVAR(arc, burst_healing_hps);
373                                         rootarmor = WEP_CVAR(arc, burst_healing_aps);
374                                 }
375                                 else
376                                 {
377                                         roothealth = WEP_CVAR(arc, beam_healing_hps);
378                                         rootarmor = WEP_CVAR(arc, beam_healing_aps);
379                                 }
380
381                                 if(trace_ent.health <= WEP_CVAR(arc, beam_healing_hmax) && roothealth)
382                                 {
383                                         trace_ent.health = min(
384                                                 trace_ent.health + (roothealth * coefficient),
385                                                 WEP_CVAR(arc, beam_healing_hmax)
386                                         );
387                                 }
388                                 if(trace_ent.armorvalue <= WEP_CVAR(arc, beam_healing_amax) && rootarmor)
389                                 {
390                                         trace_ent.armorvalue = min(
391                                                 trace_ent.armorvalue + (rootarmor * coefficient),
392                                                 WEP_CVAR(arc, beam_healing_amax)
393                                         );
394                                 }
395
396                                 // stop rot, set visual effect
397                                 if(roothealth || rootarmor)
398                                 {
399                                         trace_ent.pauserothealth_finished = max(
400                                                 trace_ent.pauserothealth_finished,
401                                                 time + autocvar_g_balance_pause_health_rot
402                                         );
403                                         trace_ent.pauserotarmor_finished = max(
404                                                 trace_ent.pauserotarmor_finished,
405                                                 time + autocvar_g_balance_pause_armor_rot
406                                         );
407                                         new_beam_type = ARC_BT_HEAL;
408                                 }
409                         }
410                         else
411                         {
412                                 float rootdamage;
413                                 if(is_player)
414                                 {
415                                         if(burst)
416                                                 { rootdamage = WEP_CVAR(arc, burst_damage); }
417                                         else
418                                                 { rootdamage = WEP_CVAR(arc, beam_damage); }
419                                 }
420                                 else
421                                         { rootdamage = WEP_CVAR(arc, beam_nonplayerdamage); }
422
423                                 if(accuracy_isgooddamage(self.owner, trace_ent))
424                                 {
425                                         accuracy_add(
426                                                 self.owner,
427                                                 WEP_ARC,
428                                                 0,
429                                                 rootdamage * coefficient * falloff
430                                         );
431                                 }
432
433                                 Damage(
434                                         trace_ent,
435                                         self.owner,
436                                         self.owner,
437                                         rootdamage * coefficient * falloff,
438                                         WEP_ARC,
439                                         hitorigin,
440                                         WEP_CVAR(arc, beam_force) * new_dir * coefficient * falloff
441                                 );
442
443                                 new_beam_type = ARC_BT_HIT;
444                         }
445                         break; 
446                 }
447                 else if(trace_fraction != 1)
448                 {
449                         // we collided with geometry
450                         new_beam_type = ARC_BT_WALL;
451                         break;
452                 }
453                 else
454                 {
455                         last_origin = new_origin;
456                 }
457         }
458
459         // if we're bursting, use burst visual effects
460         new_beam_type += burst;
461
462         // network information: beam type
463         if(new_beam_type != self.beam_type)
464         {
465                 self.SendFlags |= 16;
466                 self.beam_type = new_beam_type;
467         }
468
469         self.owner.beam_prev = time;
470         self.nextthink = time;
471 }
472
473 void W_Arc_Beam(float burst)
474 {
475         // only play fire sound if 1 sec has passed since player let go the fire button
476         if(time - self.beam_prev > 1)
477         {
478                 sound(self, CH_WEAPON_A, "weapons/lgbeam_fire.wav", VOL_BASE, ATTN_NORM);
479         }
480
481         entity beam = self.arc_beam = spawn();
482         beam.classname = "W_Arc_Beam";
483         beam.solid = SOLID_NOT;
484         beam.think = W_Arc_Beam_Think;
485         beam.owner = self;
486         beam.movetype = MOVETYPE_NONE;
487         beam.bot_dodge = TRUE;
488         beam.bot_dodgerating = WEP_CVAR(arc, beam_damage);
489         beam.beam_bursting = burst;
490         Net_LinkEntity(beam, FALSE, 0, W_Arc_Beam_Send);
491
492         entity oldself = self;
493         self = beam;
494         self.think();
495         self = oldself;
496 }
497
498 float W_Arc(float req)
499 {
500         switch(req)
501         {
502                 case WR_AIM:
503                 {
504                         if(WEP_CVAR(arc, beam_botaimspeed))
505                                 self.BUTTON_ATCK = bot_aim(WEP_CVAR(arc, beam_botaimspeed), 0, WEP_CVAR(arc, beam_botaimlifetime), FALSE);
506                         else
507                                 self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE);
508                         return TRUE;
509                 }
510                 case WR_THINK:
511                 {
512                         #if 0
513                         if(self.arc_beam.beam_heat > threshold)
514                         {
515                                 stop the beam somehow
516                                 play overheat animation
517                         }
518                         #endif
519
520                         if(self.BUTTON_ATCK || self.BUTTON_ATCK2 || self.arc_beam.beam_bursting)
521                         {
522                                 if(self.BUTTON_ATCK_prev) // TODO: Find another way to implement this!
523                                         /*if(self.animstate_startframe == self.anim_shoot_x && self.animstate_numframes == self.anim_shoot_y)
524                                                 weapon_thinkf(WFRAME_DONTCHANGE, autocvar_g_balance_arc_primary_animtime, w_ready);
525                                         else*/
526                                                 weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
527
528                                 if((!self.arc_beam) || wasfreed(self.arc_beam))
529                                 {
530                                         if(weapon_prepareattack(!!self.BUTTON_ATCK2, 0))
531                                         {
532                                                 W_Arc_Beam(!!self.BUTTON_ATCK2);
533                                                 
534                                                 if(!self.BUTTON_ATCK_prev)
535                                                 {
536                                                         weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
537                                                         self.BUTTON_ATCK_prev = 1;
538                                                 }
539                                         }
540                                 }
541                         } 
542                         else // todo
543                         {
544                                 if(self.BUTTON_ATCK_prev != 0)
545                                 {
546                                         weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
547                                         ATTACK_FINISHED(self) = time + WEP_CVAR(arc, beam_refire) * W_WeaponRateFactor();
548                                 }
549                                 self.BUTTON_ATCK_prev = 0;
550                         }
551
552                         #if 0
553                         if(self.BUTTON_ATCK2)
554                         if(weapon_prepareattack(1, autocvar_g_balance_arc_secondary_refire))
555                         {
556                                 W_Arc_Attack2();
557                                 self.arc_count = autocvar_g_balance_arc_secondary_count;
558                                 weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_arc_secondary_animtime, w_arc_checkattack);
559                                 self.arc_secondarytime = time + autocvar_g_balance_arc_secondary_refire2 * W_WeaponRateFactor();
560                         }
561                         #endif
562
563                         return TRUE;
564                 }
565                 case WR_INIT:
566                 {
567                         precache_model("models/weapons/g_arc.md3");
568                         precache_model("models/weapons/v_arc.md3");
569                         precache_model("models/weapons/h_arc.iqm");
570                         //precache_sound("weapons/arc_fire.wav");
571                         //precache_sound("weapons/arc_fire2.wav");
572                         //precache_sound("weapons/arc_impact.wav");
573                         if(!arc_shotorigin[0])
574                         {
575                                 arc_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 1);
576                                 arc_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 2);
577                                 arc_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 3);
578                                 arc_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 4);
579                         }
580                         ARC_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP)
581                         return TRUE;
582                 }
583                 case WR_CHECKAMMO1:
584                 {
585                         return ((!WEP_CVAR(arc, beam_ammo)) || (self.WEP_AMMO(ARC) > 0));
586                 }
587                 case WR_CHECKAMMO2:
588                 {
589                         return ((!WEP_CVAR(arc, burst_ammo)) || (self.WEP_AMMO(ARC) > 0));
590                 }
591                 case WR_CONFIG:
592                 {
593                         ARC_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS)
594                         return TRUE;
595                 }
596                 case WR_KILLMESSAGE:
597                 {
598                         if(w_deathtype & HITTYPE_SECONDARY)
599                         {
600                                 return WEAPON_ELECTRO_MURDER_ORBS;
601                         }
602                         else
603                         {
604                                 if(w_deathtype & HITTYPE_BOUNCE)
605                                         return WEAPON_ELECTRO_MURDER_COMBO;
606                                 else
607                                         return WEAPON_ELECTRO_MURDER_BOLT;
608                         }
609                 }
610         }
611         return FALSE;
612 }
613 #endif
614 #ifdef CSQC
615 float W_Arc(float req)
616 {
617         switch(req)
618         {
619                 case WR_IMPACTEFFECT:
620                 {
621                         // todo
622                         return TRUE;
623                 }
624                 case WR_INIT:
625                 {
626                         //precache_sound("weapons/arc_impact.wav");
627                         //precache_sound("weapons/arc_impact_combo.wav");
628                         return TRUE;
629                 }
630                 case WR_ZOOMRETICLE:
631                 {
632                         // no weapon specific image for this weapon
633                         return FALSE;
634                 }
635         }
636         return FALSE;
637 }
638 #endif
639 #endif