]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/w_rocketlauncher.qc
Merge branch 'master' into mirceakitsune/universal_reload_system
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / w_rocketlauncher.qc
1 #ifdef REGISTER_WEAPON
2 REGISTER_WEAPON(ROCKET_LAUNCHER, w_rlauncher, IT_ROCKETS, 9, WEP_FLAG_NORMAL | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, BOT_PICKUP_RATING_HIGH, "rl", "rocketlauncher", _("Rocket Launcher"))
3 #else
4 #ifdef SVQC
5 .float rl_release;
6 .float rl_detonate_later;
7
8 // weapon load persistence, for weapons that support reloading
9 .float rocketlauncher_load;
10
11 void W_RocketLauncher_SetAmmoCounter()
12 {
13         // set clip_load to the weapon we have switched to, if the gun uses reloading
14         if(!autocvar_g_balance_rocketlauncher_reload_ammo)
15                 self.clip_load = 0; // also keeps crosshair ammo from displaying
16         else
17         {
18                 self.clip_load = self.rocketlauncher_load;
19                 self.clip_size = autocvar_g_balance_rocketlauncher_reload_ammo; // for the crosshair ammo display
20         }
21 }
22
23 void W_RocketLauncher_ReloadedAndReady()
24 {
25         float t;
26
27         // now do the ammo transfer
28         self.clip_load = self.old_clip_load; // restore the ammo counter, in case we still had ammo in the weapon before reloading
29         while(self.clip_load < autocvar_g_balance_rocketlauncher_reload_ammo && self.ammo_rockets) // make sure we don't add more ammo than we have
30         {
31                 self.clip_load += 1;
32                 self.ammo_rockets -= 1;
33         }
34         self.rocketlauncher_load = self.clip_load;
35
36         t = ATTACK_FINISHED(self) - autocvar_g_balance_rocketlauncher_reload_time - 1;
37         ATTACK_FINISHED(self) = t;
38         w_ready();
39 }
40
41 void W_RocketLauncher_Reload()
42 {
43         // return if reloading is disabled for this weapon
44         if(!autocvar_g_balance_rocketlauncher_reload_ammo)
45                 return;
46
47         if(!W_ReloadCheck(self.ammo_rockets, autocvar_g_balance_rocketlauncher_ammo))
48                 return;
49
50         float t;
51
52         sound (self, CHAN_WEAPON2, "weapons/reload.wav", VOL_BASE, ATTN_NORM);
53
54         t = max(time, ATTACK_FINISHED(self)) + autocvar_g_balance_rocketlauncher_reload_time + 1;
55         ATTACK_FINISHED(self) = t;
56
57         weapon_thinkf(WFRAME_RELOAD, autocvar_g_balance_rocketlauncher_reload_time, W_RocketLauncher_ReloadedAndReady);
58
59         self.old_clip_load = self.clip_load;
60         self.clip_load = -1;
61 }
62
63 void W_Rocket_Unregister()
64 {
65         if(self.owner && self.owner.lastrocket == self)
66         {
67                 self.owner.lastrocket = world;
68                 // self.owner.rl_release = 1;
69         }
70 }
71
72 void W_Rocket_Explode ()
73 {
74         W_Rocket_Unregister();
75
76         if(other.takedamage == DAMAGE_AIM)
77                 if(other.classname == "player")
78                         if(IsDifferentTeam(self.owner, other))
79                                 if(IsFlying(other))
80                                         AnnounceTo(self.owner, "airshot");
81
82         self.event_damage = SUB_Null;
83         self.takedamage = DAMAGE_NO;
84
85         RadiusDamage (self, self.owner, autocvar_g_balance_rocketlauncher_damage, autocvar_g_balance_rocketlauncher_edgedamage, autocvar_g_balance_rocketlauncher_radius, world, autocvar_g_balance_rocketlauncher_force, self.projectiledeathtype, other);
86
87         if (self.owner.weapon == WEP_ROCKET_LAUNCHER)
88         {
89                 if(self.owner.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo)
90                 {
91                         self.owner.cnt = WEP_ROCKET_LAUNCHER;
92                         ATTACK_FINISHED(self.owner) = time;
93                         self.owner.switchweapon = w_getbestweapon(self.owner);
94                 }
95         }
96         remove (self);
97 }
98
99 void W_Rocket_DoRemoteExplode ()
100 {
101         W_Rocket_Unregister();
102
103         self.event_damage = SUB_Null;
104         self.takedamage = DAMAGE_NO;
105
106         RadiusDamage (self, self.owner, autocvar_g_balance_rocketlauncher_remote_damage, autocvar_g_balance_rocketlauncher_remote_edgedamage, autocvar_g_balance_rocketlauncher_remote_radius, world, autocvar_g_balance_rocketlauncher_remote_force, self.projectiledeathtype | HITTYPE_BOUNCE, world);
107
108         if (self.owner.weapon == WEP_ROCKET_LAUNCHER)
109         {
110                 if(self.owner.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo)
111                 {
112                         self.owner.cnt = WEP_ROCKET_LAUNCHER;
113                         ATTACK_FINISHED(self.owner) = time;
114                         self.owner.switchweapon = w_getbestweapon(self.owner);
115                 }
116         }
117         remove (self);
118 }
119
120 entity FindLaserTarget(entity e, float dist_variance, float dot_variance)
121 {
122         entity head, selected;
123         vector dir;
124         float dist, maxdist,// bestdist,
125                 dot,// bestdot,
126                 points, bestpoints;
127         //bestdist = 9999;
128         //bestdot = -2;
129         bestpoints = 0;
130         maxdist = 800;
131         selected = world;
132
133         makevectors(e.angles);
134
135         head = find(world, classname, "laser_target");
136         while(head)
137         {
138                 points = 0;
139                 dir = normalize(head.origin - self.origin);
140                 dot = dir * v_forward;
141                 dist = vlen(head.origin - self.origin);
142                 if(dist > maxdist)
143                         dist = maxdist;
144
145                 // gain points for being in front
146                 points = points + ((dot+1)*0.5) * 500
147                         * (1 + crandom()*dot_variance);
148                 // gain points for being close away
149                 points = points + (1 - dist/maxdist) * 1000
150                         * (1 + crandom()*dot_variance);
151
152                 traceline(e.origin, head.origin, TRUE, self);
153                 if(trace_fraction < 1)
154                 {
155                         points = 0;
156                 }
157
158                 if(points > bestpoints)//random() > 0.5)//
159                 {
160                         bestpoints = points;
161                         selected = head;
162                 }
163
164                 head = find(head, classname, "laser_target");
165         }
166
167         //bprint(selected.owner.netname);
168         //bprint("\n");
169         return selected;
170 }
171
172 void W_Rocket_RemoteExplode()
173 {
174         if(self.owner.deadflag == DEAD_NO)
175         if(self.owner.lastrocket)
176         {
177                 if((self.spawnshieldtime >= 0)
178                         ? (time >= self.spawnshieldtime) // timer
179                         : (vlen(NearestPointOnBox(self.owner, self.origin) - self.origin) > autocvar_g_balance_rocketlauncher_remote_radius) // safety device
180                 )
181                 {
182                         W_Rocket_DoRemoteExplode();
183                 }
184         }
185 }
186
187 vector rocket_steerto(vector thisdir, vector goaldir, float maxturn_cos)
188 {
189         if(thisdir * goaldir > maxturn_cos)
190                 return goaldir;
191         float f, m2;
192         vector v;
193         // solve:
194         //   g = normalize(thisdir + goaldir * X)
195         //   thisdir * g = maxturn
196         //
197         //   gg = thisdir + goaldir * X
198         //   (thisdir * gg)^2 = maxturn^2 * (gg * gg)
199         //
200         //   (1 + (thisdir * goaldir) * X)^2 = maxturn^2 * (1 + X*X + 2 * X * thisdir * goaldir)
201         f = thisdir * goaldir;
202         //   (1 + f * X)^2 = maxturn^2 * (1 + X*X + 2 * X * f)
203         //   0 = (m^2 - f^2) * x^2 + (2 * f * (m^2 - 1)) * x + (m^2 - 1)
204         m2 = maxturn_cos * maxturn_cos;
205         v = solve_quadratic(m2 - f * f, 2 * f * (m2 - 1), m2 - 1);
206         return normalize(thisdir + goaldir * v_y); // the larger solution!
207 }
208
209 void W_Rocket_Think (void)
210 {
211         vector desireddir, olddir, newdir, desiredorigin, goal;
212 #if 0
213         float cosminang, cosmaxang, cosang;
214 #endif
215         float velspeed, f;
216         self.nextthink = time;
217         if (time > self.cnt)
218         {
219                 other = world;
220                 self.projectiledeathtype |= HITTYPE_BOUNCE;
221                 W_Rocket_Explode ();
222                 return;
223         }
224
225         // accelerate
226         makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0');
227         velspeed = autocvar_g_balance_rocketlauncher_speed * g_weaponspeedfactor - (self.velocity * v_forward);
228         if (velspeed > 0)
229                 self.velocity = self.velocity + v_forward * min(autocvar_g_balance_rocketlauncher_speedaccel * g_weaponspeedfactor * frametime, velspeed);
230
231         // laser guided, or remote detonation
232         if (self.owner.weapon == WEP_ROCKET_LAUNCHER)
233         {
234                 if(self == self.owner.lastrocket)
235                 if not(self.owner.rl_release)
236                 if not(self.BUTTON_ATCK2)
237                 if(autocvar_g_balance_rocketlauncher_guiderate)
238                 if(time > self.pushltime)
239                 if(self.owner.deadflag == DEAD_NO)
240                 {
241                         f = autocvar_g_balance_rocketlauncher_guideratedelay;
242                         if(f)
243                                 f = bound(0, (time - self.pushltime) / f, 1);
244                         else
245                                 f = 1;
246
247                         velspeed = vlen(self.velocity);
248
249                         makevectors(self.owner.v_angle);
250                         desireddir = WarpZone_RefSys_TransformVelocity(self.owner, self, v_forward);
251                         desiredorigin = WarpZone_RefSys_TransformOrigin(self.owner, self, self.owner.origin + self.owner.view_ofs);
252                         olddir = normalize(self.velocity);
253
254                         // now it gets tricky... we want to move like some curve to approximate the target direction
255                         // but we are limiting the rate at which we can turn!
256                         goal = desiredorigin + ((self.origin - desiredorigin) * desireddir + autocvar_g_balance_rocketlauncher_guidegoal) * desireddir;
257                         newdir = rocket_steerto(olddir, normalize(goal - self.origin), cos(autocvar_g_balance_rocketlauncher_guiderate * f * frametime * DEG2RAD));
258
259                         self.velocity = newdir * velspeed;
260                         self.angles = vectoangles(self.velocity);
261
262                         if(!self.count)
263                         {
264                                 pointparticles(particleeffectnum("rocket_guide"), self.origin, self.velocity, 1);
265                                 // TODO add a better sound here
266                                 sound (self.owner, CHAN_WEAPON2, "weapons/rocket_mode.wav", VOL_BASE, ATTN_NORM);
267                                 self.count = 1;
268                         }
269                 }
270
271                 if(self.rl_detonate_later)
272                         W_Rocket_RemoteExplode();
273         }
274
275         if(self.csqcprojectile_clientanimate == 0)
276                 UpdateCSQCProjectile(self);
277 }
278
279 void W_Rocket_Touch (void)
280 {
281         if(WarpZone_Projectile_Touch())
282         {
283                 if(wasfreed(self))
284                         W_Rocket_Unregister();
285                 return;
286         }
287         W_Rocket_Unregister();
288         W_Rocket_Explode ();
289 }
290
291 void W_Rocket_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
292 {
293         if (self.health <= 0)
294                 return;
295         self.health = self.health - damage;
296         self.angles = vectoangles(self.velocity);
297         if (self.health <= 0)
298                 W_PrepareExplosionByDamage(attacker, W_Rocket_Explode);
299 }
300
301 void W_Rocket_Attack (void)
302 {
303         local entity missile;
304         local entity flash;
305
306         // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
307         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
308         {
309                 if(autocvar_g_balance_rocketlauncher_reload_ammo)
310                 {
311                         self.clip_load -= autocvar_g_balance_rocketlauncher_ammo;
312                         self.rocketlauncher_load = self.clip_load;
313                 }
314                 else
315                         self.ammo_rockets -= autocvar_g_balance_rocketlauncher_ammo;
316         }
317
318         W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 5, "weapons/rocket_fire.wav", CHAN_WEAPON, autocvar_g_balance_rocketlauncher_damage);
319         pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
320
321         missile = WarpZone_RefSys_SpawnSameRefSys(self);
322         missile.owner = self;
323         self.lastrocket = missile;
324         if(autocvar_g_balance_rocketlauncher_detonatedelay >= 0)
325                 missile.spawnshieldtime = time + autocvar_g_balance_rocketlauncher_detonatedelay;
326         else
327                 missile.spawnshieldtime = -1;
328         missile.pushltime = time + autocvar_g_balance_rocketlauncher_guidedelay;
329         missile.classname = "rocket";
330         missile.bot_dodge = TRUE;
331         missile.bot_dodgerating = autocvar_g_balance_rocketlauncher_damage * 2; // * 2 because it can be detonated inflight which makes it even more dangerous
332
333         missile.takedamage = DAMAGE_YES;
334         missile.damageforcescale = autocvar_g_balance_rocketlauncher_damageforcescale;
335         missile.health = autocvar_g_balance_rocketlauncher_health;
336         missile.event_damage = W_Rocket_Damage;
337
338         missile.movetype = MOVETYPE_FLY;
339         PROJECTILE_MAKETRIGGER(missile);
340         missile.projectiledeathtype = WEP_ROCKET_LAUNCHER;
341         setsize (missile, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot
342
343         setorigin (missile, w_shotorg - v_forward * 3); // move it back so it hits the wall at the right point
344         W_SetupProjectileVelocity(missile, autocvar_g_balance_rocketlauncher_speedstart, 0);
345         missile.angles = vectoangles (missile.velocity);
346
347         missile.touch = W_Rocket_Touch;
348         missile.think = W_Rocket_Think;
349         missile.nextthink = time;
350         missile.cnt = time + autocvar_g_balance_rocketlauncher_lifetime;
351         missile.flags = FL_PROJECTILE;
352
353         CSQCProjectile(missile, autocvar_g_balance_rocketlauncher_guiderate == 0 && autocvar_g_balance_rocketlauncher_speedaccel == 0, PROJECTILE_ROCKET, FALSE); // because of fly sound
354
355         // muzzle flash for 1st person view
356         flash = spawn ();
357         setmodel (flash, "models/flash.md3"); // precision set below
358         SUB_SetFade (flash, time, 0.1);
359         flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
360         W_AttachToShotorg(flash, '5 0 0');
361
362         // common properties
363         other = missile; MUTATOR_CALLHOOK(EditProjectile);
364 }
365
366 void spawnfunc_weapon_rocketlauncher (void); // defined in t_items.qc
367
368 float w_rlauncher(float req)
369 {
370         entity rock;
371         float rockfound;
372         float ammo_amount;
373
374         if (req == WR_AIM)
375         {
376                 // aim and decide to fire if appropriate
377                 self.BUTTON_ATCK = bot_aim(autocvar_g_balance_rocketlauncher_speed, 0, autocvar_g_balance_rocketlauncher_lifetime, FALSE);
378                 if(skill >= 2) // skill 0 and 1 bots won't detonate rockets!
379                 {
380                         // decide whether to detonate rockets
381                         local entity missile, targetlist, targ;
382                         local float edgedamage, coredamage, edgeradius, recipricoledgeradius, d;
383                         local float selfdamage, teamdamage, enemydamage;
384                         edgedamage = autocvar_g_balance_rocketlauncher_edgedamage;
385                         coredamage = autocvar_g_balance_rocketlauncher_damage;
386                         edgeradius = autocvar_g_balance_rocketlauncher_radius;
387                         recipricoledgeradius = 1 / edgeradius;
388                         selfdamage = 0;
389                         teamdamage = 0;
390                         enemydamage = 0;
391                         targetlist = findchainfloat(bot_attack, TRUE);
392                         missile = find(world, classname, "rocket");
393                         while (missile)
394                         {
395                                 if (missile.owner != self)
396                                 {
397                                         missile = find(missile, classname, "rocket");
398                                         continue;
399                                 }
400                                 targ = targetlist;
401                                 while (targ)
402                                 {
403                                         d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - missile.origin);
404                                         d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000);
405                                         // count potential damage according to type of target
406                                         if (targ == self)
407                                                 selfdamage = selfdamage + d;
408                                         else if (targ.team == self.team && teams_matter)
409                                                 teamdamage = teamdamage + d;
410                                         else if (bot_shouldattack(targ))
411                                                 enemydamage = enemydamage + d;
412                                         targ = targ.chain;
413                                 }
414                                 missile = find(missile, classname, "rocket");
415                         }
416                         local float desirabledamage;
417                         desirabledamage = enemydamage;
418                         if (time > self.invincible_finished && time > self.spawnshieldtime)
419                                 desirabledamage = desirabledamage - selfdamage * autocvar_g_balance_selfdamagepercent;
420                         if (teams_matter && self.team)
421                                 desirabledamage = desirabledamage - teamdamage;
422
423                         missile = find(world, classname, "rocket");
424                         while (missile)
425                         {
426                                 if (missile.owner != self)
427                                 {
428                                         missile = find(missile, classname, "rocket");
429                                         continue;
430                                 }
431                                 makevectors(missile.v_angle);
432                                 targ = targetlist;
433                                 if (skill > 9) // normal players only do this for the target they are tracking
434                                 {
435                                         targ = targetlist;
436                                         while (targ)
437                                         {
438                                                 if (
439                                                         (v_forward * normalize(missile.origin - targ.origin)< 0.1)
440                                                         && desirabledamage > 0.1*coredamage
441                                                 )self.BUTTON_ATCK2 = TRUE;
442                                                 targ = targ.chain;
443                                         }
444                                 }else{
445                                         local float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000);
446                                         //As the distance gets larger, a correct detonation gets near imposible
447                                         //Bots are assumed to use the rocket spawnfunc_light to see if the rocket gets near a player
448                                         if(v_forward * normalize(missile.origin - self.enemy.origin)< 0.1)
449                                                 if(self.enemy.classname == "player")
450                                                         if(desirabledamage >= 0.1*coredamage)
451                                                                 if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1))
452                                                                         self.BUTTON_ATCK2 = TRUE;
453                                 //      dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n");
454                                 }
455
456                                 missile = find(missile, classname, "rocket");
457                         }
458                         // if we would be doing at X percent of the core damage, detonate it
459                         // but don't fire a new shot at the same time!
460                         if (desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events
461                                 self.BUTTON_ATCK2 = TRUE;
462                         if ((skill > 6.5) && (selfdamage > self.health))
463                                 self.BUTTON_ATCK2 = FALSE;
464                         //if(self.BUTTON_ATCK2 == TRUE)
465                         //      dprint(ftos(desirabledamage),"\n");
466                         if (self.BUTTON_ATCK2 == TRUE) self.BUTTON_ATCK = FALSE;
467                 }
468         }
469         else if (req == WR_THINK)
470         {
471                 if(autocvar_g_balance_rocketlauncher_reload_ammo && self.clip_load < autocvar_g_balance_rocketlauncher_ammo) // forced reload
472                         W_RocketLauncher_Reload();
473                 else
474                 {
475                         if (self.BUTTON_ATCK)
476                         {
477                                 if(self.rl_release || autocvar_g_balance_rocketlauncher_guidestop)
478                                 if(weapon_prepareattack(0, autocvar_g_balance_rocketlauncher_refire))
479                                 {
480                                         W_Rocket_Attack();
481                                         weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_rocketlauncher_animtime, w_ready);
482                                         self.rl_release = 0;
483                                 }
484                         }
485                         else
486                                 self.rl_release = 1;
487
488                         if (self.BUTTON_ATCK2)
489                         {
490                                 rockfound = 0;
491                                 for(rock = world; (rock = find(rock, classname, "rocket")); ) if(rock.owner == self)
492                                 {
493                                         if(!rock.rl_detonate_later)
494                                         {
495                                                 rock.rl_detonate_later = TRUE;
496                                                 rockfound = 1;
497                                         }
498                                 }
499                                 if(rockfound)
500                                         sound (self, CHAN_WEAPON2, "weapons/rocket_det.wav", VOL_BASE, ATTN_NORM);
501                         }
502                 }
503         }
504         else if (req == WR_PRECACHE)
505         {
506                 precache_model ("models/flash.md3");
507                 precache_model ("models/weapons/g_rl.md3");
508                 precache_model ("models/weapons/v_rl.md3");
509                 precache_model ("models/weapons/h_rl.iqm");
510                 precache_sound ("weapons/rocket_det.wav");
511                 precache_sound ("weapons/rocket_fire.wav");
512                 precache_sound ("weapons/rocket_mode.wav");
513                 precache_sound ("weapons/reload.wav");
514         }
515         else if (req == WR_SETUP)
516         {
517                 weapon_setup(WEP_ROCKET_LAUNCHER);
518                 W_RocketLauncher_SetAmmoCounter();
519                 self.rl_release = 1;
520         }
521         else if (req == WR_CHECKAMMO1)
522         {
523                 // don't switch while guiding a missile
524                 if (ATTACK_FINISHED(self) <= time || self.weapon != WEP_ROCKET_LAUNCHER)
525                 {
526                         if(self.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo)
527                                 ammo_amount = FALSE;
528                         if(autocvar_g_balance_electro_reload_ammo && self.minelayer_load < autocvar_g_balance_rocketlauncher_ammo)
529                                 ammo_amount = FALSE;
530                 }
531                 return ammo_amount;
532         }
533         else if (req == WR_CHECKAMMO2)
534                 return FALSE;
535         else if (req == WR_RESETPLAYER)
536         {
537                 self.rl_release = 0;
538
539                 // all weapons must be fully loaded when we spawn
540                 self.rocketlauncher_load = autocvar_g_balance_rocketlauncher_reload_ammo;
541         }
542         else if (req == WR_RELOAD)
543         {
544                 W_RocketLauncher_Reload();
545         }
546         return TRUE;
547 };
548 #endif
549 #ifdef CSQC
550 float w_rlauncher(float req)
551 {
552         if(req == WR_IMPACTEFFECT)
553         {
554                 vector org2;
555                 org2 = w_org + w_backoff * 12;
556                 pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1);
557                 if(!w_issilent)
558                         sound(self, CHAN_PROJECTILE, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
559         }
560         else if(req == WR_PRECACHE)
561         {
562                 precache_sound("weapons/rocket_impact.wav");
563         }
564         else if (req == WR_SUICIDEMESSAGE)
565                 w_deathtypestring = _("%s exploded");
566         else if (req == WR_KILLMESSAGE)
567         {
568                 if(w_deathtype & HITTYPE_BOUNCE) // (remote detonation)
569                         w_deathtypestring = _("%s got too close to %s's rocket");
570                 else if(w_deathtype & HITTYPE_SPLASH)
571                         w_deathtypestring = _("%s almost dodged %s's rocket");
572                 else
573                         w_deathtypestring = _("%s ate %s's rocket");
574         }
575         return TRUE;
576 }
577 #endif
578 #endif