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