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