]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/weapons/w_arc.qc
d6077050f88ee3a269947b623d308b6eddeac3ef
[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_maxangle) \
32         w_cvar(id, sn, NONE, beam_nonplayerdamage) \
33         w_cvar(id, sn, NONE, beam_range) \
34         w_cvar(id, sn, NONE, beam_refire) \
35         w_cvar(id, sn, NONE, beam_returnspeed) \
36         w_prop(id, sn, float,  switchdelay_raise, switchdelay_raise) \
37         w_prop(id, sn, float,  switchdelay_drop, switchdelay_drop) \
38         w_prop(id, sn, string, weaponreplace, weaponreplace) \
39         w_prop(id, sn, float,  weaponstart, weaponstart) \
40         w_prop(id, sn, float,  weaponstartoverride, weaponstartoverride) \
41         w_prop(id, sn, float,  weaponthrowable, weaponthrowable)
42
43 #ifndef MENUQC
44 vector arc_shotorigin[4];
45 .vector beam_start;
46 .vector beam_dir;
47 .vector beam_wantdir;
48 .float beam_type;
49 #define ARC_BT_MISS        0
50 #define ARC_BT_WALL        1
51 #define ARC_BT_HEAL        2
52 #define ARC_BT_HIT         3
53 #define ARC_BT_BURST_MISS  10
54 #define ARC_BT_BURST_WALL  11
55 #define ARC_BT_BURST_HEAL  12
56 #define ARC_BT_BURST_HIT   13
57 #define ARC_BT_BURSTMASK   10
58 #endif
59 #ifdef SVQC
60 #define ARC_MAX_SEGMENTS 20
61 ARC_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
62 void ArcInit(void);
63 .entity arc_beam; // used for beam
64 .float BUTTON_ATCK_prev; // for better animation control
65 .float lg_fire_prev; // for better animation control
66 #ifdef ARC_DEBUG
67 .entity lg_ents[ARC_MAX_SEGMENTS]; // debug
68 #endif
69 .float beam_initialized;
70 #endif
71 #else
72 #ifdef SVQC
73 void spawnfunc_weapon_arc(void) { weapon_defaultspawnfunc(WEP_ARC); }
74
75 float W_Arc_Beam_Send(entity to, float sf)
76 {
77         WriteByte(MSG_ENTITY, ENT_CLIENT_ARC_BEAM);
78
79         // don't send group 1, 2, or 3 if this beam is for the local player
80         if(to == self.owner) { sf &= ~7; }
81         WriteByte(MSG_ENTITY, sf);
82         
83         if(sf & 1) // starting location // not sent if beam is for owner
84         {
85                 //WriteByte(MSG_ENTITY, num_for_edict(self.owner));
86                 WriteCoord(MSG_ENTITY, self.beam_start_x);
87                 WriteCoord(MSG_ENTITY, self.beam_start_y);
88                 WriteCoord(MSG_ENTITY, self.beam_start_z);
89                 //WriteCoord(MSG_ENTITY, WEP_CVAR(arc, beam_range));
90         }
91         if(sf & 2) // want/aim direction
92         {
93                 WriteCoord(MSG_ENTITY, self.beam_wantdir_x);
94                 WriteCoord(MSG_ENTITY, self.beam_wantdir_y);
95                 WriteCoord(MSG_ENTITY, self.beam_wantdir_z);
96         }
97         if(sf & 4) // beam direction
98         {
99                 WriteCoord(MSG_ENTITY, self.beam_dir_x);
100                 WriteCoord(MSG_ENTITY, self.beam_dir_y);
101                 WriteCoord(MSG_ENTITY, self.beam_dir_z);
102         }
103         if(sf & 8) // beam type
104         {
105                 WriteByte(MSG_ENTITY, self.beam_type);
106         }
107         return TRUE;
108 }
109
110 void W_Arc_Beam_Think(void)
111 {
112         float i, burst = TRUE;
113         if(self != self.owner.arc_beam)
114         {
115                 #ifdef ARC_DEBUG
116                 if(self.lg_ents[0])
117                 {
118                         for(i = 0; i < ARC_MAX_SEGMENTS; ++i)
119                                 remove(self.lg_ents[i]);
120                 }
121                 print("W_Arc_Beam_Think(): EXPIRING BEAM #1\n");
122                 #endif
123                 remove(self);
124                 return;
125         }
126         if((self.owner.WEP_AMMO(ARC) <= 0 && !(self.owner.items & IT_UNLIMITED_WEAPON_AMMO)) || self.owner.deadflag != DEAD_NO || !self.owner.BUTTON_ATCK || self.owner.freezetag_frozen)
127         {
128                 if(self == self.owner.arc_beam) { self.owner.arc_beam = world; } // is this needed? I thought this is changed to world when removed ANYWAY
129                 #ifdef ARC_DEBUG
130                 if(self.lg_ents[0])
131                 {
132                         for(i = 0; i < ARC_MAX_SEGMENTS; ++i)
133                                 remove(self.lg_ents[i]);
134                 }
135                 print("W_Arc_Beam_Think(): EXPIRING BEAM #2\n");
136                 #endif
137                 remove(self);
138                 return;
139         }
140
141         if(self.owner.BUTTON_ATCK2)
142         {
143                 burst = ARC_BT_BURSTMASK;
144         }
145
146         // decrease ammo // todo: support burst ammo
147         float dt = frametime;
148         if(!(self.owner.items & IT_UNLIMITED_WEAPON_AMMO))
149         {
150                 if(WEP_CVAR(arc, beam_ammo))
151                 {
152                         dt = min(dt, self.owner.WEP_AMMO(ARC) / WEP_CVAR(arc, beam_ammo));
153                         self.owner.WEP_AMMO(ARC) = max(0, self.owner.WEP_AMMO(ARC) - WEP_CVAR(arc, beam_ammo) * frametime);
154                 }
155         }
156
157         makevectors(self.owner.v_angle);
158
159         W_SetupShot_Range(self.owner, TRUE, 0, "", 0, WEP_CVAR(arc, beam_damage) * dt, WEP_CVAR(arc, beam_range));
160
161         //printf("SERVER direction: %s\n", vtos(w_shotdir));
162
163         // network information: shot origin and want/aim direction
164         if(self.beam_start != w_shotorg)
165         {
166                 self.SendFlags |= 1;
167                 self.beam_start = w_shotorg;
168         }
169         if(self.beam_wantdir != w_shotdir)
170         {
171                 self.SendFlags |= 2;
172                 self.beam_wantdir = w_shotdir;
173         }
174
175         if(!self.beam_initialized)
176         {
177                 #ifdef ARC_DEBUG
178                 for(i = 0; i < ARC_MAX_SEGMENTS; ++i)
179                         self.lg_ents[i] = spawn();
180                 #endif
181                 
182                 self.beam_dir = w_shotdir;
183                 self.beam_initialized = TRUE;
184         }
185
186         float segments; 
187         if(self.beam_dir != w_shotdir)
188         {
189                 float angle = ceil(vlen(w_shotdir - self.beam_dir) * RAD2DEG);
190                 float anglelimit;
191                 if(angle && (angle > WEP_CVAR(arc, beam_maxangle)))
192                 {
193                         // if the angle is greater than maxangle, force the blendfactor to make this the maximum factor
194                         anglelimit = min(WEP_CVAR(arc, beam_maxangle) / angle, 1);
195                 }
196                 else
197                 {
198                         // the radius is not too far yet, no worries :D
199                         anglelimit = 1;
200                 }
201
202                 // calculate how much we're going to move the end of the beam to the want position
203                 float blendfactor = bound(0, anglelimit * (1 - (WEP_CVAR(arc, beam_returnspeed) * dt)), 1);
204                 self.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (self.beam_dir * blendfactor));
205
206                 // todo: figure out a way so that blendfactor becomes 0 at some point,
207                 // currently self.beam_dir and w_shotdir never really become equal as there is no rounding/snapping point
208                 // printf("blendfactor = %f\n", blendfactor);
209
210                 // network information: beam direction
211                 self.SendFlags |= 4;
212
213                 // calculate how many segments are needed
214                 float max_allowed_segments;
215
216                 if(WEP_CVAR(arc, beam_distancepersegment))
217                         max_allowed_segments = min(ARC_MAX_SEGMENTS, 1 + (vlen(w_shotdir / WEP_CVAR(arc, beam_distancepersegment))));
218                 else
219                         max_allowed_segments = ARC_MAX_SEGMENTS;
220
221                 if(WEP_CVAR(arc, beam_degreespersegment))
222                 {
223                         segments = min( max(1, ( min(angle, WEP_CVAR(arc, beam_maxangle)) / WEP_CVAR(arc, beam_degreespersegment) ) ), max_allowed_segments );
224                 }
225                 else { segments = 1; }
226         }
227         else
228         {
229                 segments = 1;
230         }
231
232         vector beam_endpos_estimate = (w_shotorg + (self.beam_dir * WEP_CVAR(arc, beam_range)));
233
234         #ifdef ARC_DEBUG
235         //printf("segment count: %d\n", segments);
236         //string segmentinfo = "";
237         #if 0
238         float totaldist = 0;
239         #endif
240         #endif
241
242         float new_beam_type = 0;
243         vector last_origin = w_shotorg;
244         for(i = 1; i <= segments; ++i)
245         {
246                 // calculate this on every segment to ensure that we always reach the full length of the attack
247                 float segmentblend = (i/segments);
248                 float segmentdist = vlen(beam_endpos_estimate - last_origin) * (i/segments);
249
250                 vector new_dir = normalize( (w_shotdir * (1 - segmentblend)) + (normalize(beam_endpos_estimate - last_origin) * segmentblend) );
251                 vector new_origin = last_origin + (new_dir * segmentdist);
252
253                 WarpZone_traceline_antilag(
254                         self.owner,
255                         last_origin,
256                         new_origin,
257                         MOVE_NORMAL,
258                         self.owner,
259                         ANTILAG_LATENCY(self.owner)
260                 );
261
262                 #ifdef ARC_DEBUG
263                 //APPEND_TO_STRING(segmentinfo, "^4, ", sprintf("^3org%s-dis'%f'", vtos(new_origin), segmentdist));
264                 #endif
265
266                 float is_player = (trace_ent.classname == "player" || trace_ent.classname == "body" || (trace_ent.flags & FL_MONSTER));
267                 if(trace_ent && (trace_ent.takedamage == DAMAGE_AIM) && (is_player || WEP_CVAR(arc, beam_nonplayerdamage)))
268                 {
269                         // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?)
270                         vector hitorigin = last_origin + (new_dir * segmentdist * trace_fraction);
271                         #ifdef ARC_DEBUG
272                         #if 0
273                         totaldist += segmentdist * trace_fraction;
274                         #endif
275                         #endif
276
277                         float falloff = ExponentialFalloff(
278                                 WEP_CVAR(arc, beam_falloff_mindist),
279                                 WEP_CVAR(arc, beam_falloff_maxdist),
280                                 WEP_CVAR(arc, beam_falloff_halflifedist),
281                                 vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, hitorigin) - w_shotorg)
282                         );
283
284                         if(is_player && SAME_TEAM(self.owner, trace_ent))
285                         {
286                                 // hit a team mate heal them now
287                                 #ifdef ARC_DEBUG
288                                 te_lightning1(self.lg_ents[i - 1], last_origin, hitorigin);
289                                 te_customflash(hitorigin, 80, 5, '0 1 0');
290                                 #if 0
291                                 printf(
292                                         "W_Arc_Beam_Think(): HIT TEAM MATE: "
293                                         "Hitorg: %s, Segments: %d, Distance: %f\n",
294                                         vtos(hitorigin),
295                                         i,
296                                         totaldist
297                                 );
298                                 #endif
299                                 #endif
300                                 new_beam_type = ARC_BT_HEAL;
301                         }
302                         else
303                         {
304                                 float rootdamage;
305                                 if(is_player)
306                                         rootdamage = WEP_CVAR(arc, beam_damage);
307                                 else
308                                         rootdamage = WEP_CVAR(arc, beam_nonplayerdamage);
309
310                                 if(accuracy_isgooddamage(self.owner, trace_ent))
311                                 {
312                                         accuracy_add(
313                                                 self.owner,
314                                                 WEP_ARC,
315                                                 0,
316                                                 rootdamage * dt * falloff
317                                         );
318                                 }
319
320                                 Damage(
321                                         trace_ent,
322                                         self.owner,
323                                         self.owner,
324                                         rootdamage * dt * falloff,
325                                         WEP_ARC,
326                                         hitorigin,
327                                         WEP_CVAR(arc, beam_force) * new_dir * dt * falloff
328                                 );
329
330                                 #ifdef ARC_DEBUG
331                                 te_lightning1(self.lg_ents[i - 1], last_origin, hitorigin);
332                                 te_customflash(hitorigin, 80, 5, '1 0 0');
333                                 #if 0
334                                 printf(
335                                         "W_Arc_Beam_Think(): HIT ENTITY: "
336                                         "Hitorg: %s, Segments: %d, Distance: %f\n",
337                                         vtos(hitorigin),
338                                         i,
339                                         totaldist
340                                 );
341                                 #endif
342                                 #endif
343                                 new_beam_type = ARC_BT_HIT;
344                         }
345                         break; 
346                 }
347                 else if(trace_fraction != 1)
348                 {
349                         // we collided with geometry
350                         #ifdef ARC_DEBUG
351                         te_lightning1(self.lg_ents[i - 1], last_origin, trace_endpos);
352                         te_customflash(trace_endpos, 50, 2, '0 0 1');
353                         #if 0
354                         totaldist += segmentdist * trace_fraction;
355                         printf(
356                                 "W_Arc_Beam_Think(): HIT WALL: "
357                                 "Hitorg: %s, Segments: %d, Distance: %f\n",
358                                 vtos(trace_endpos),
359                                 i,
360                                 totaldist
361                         );
362                         #endif
363                         #endif
364                         new_beam_type = ARC_BT_WALL;
365                         break;
366                 }
367                 else
368                 {
369                         #ifdef ARC_DEBUG
370                         #if 0
371                         totaldist += segmentdist;
372                         #endif
373                         te_lightning1(self.lg_ents[i - 1], last_origin, new_origin);
374                         #endif
375                         last_origin = new_origin;
376                 }
377         }
378
379         #ifdef ARC_DEBUG
380         if(!new_beam_type)
381         {
382                 #if 0
383                 printf(
384                         "W_Arc_Beam_Think(): MISS: "
385                         "Shotorg: %s, Endpos: %s, Segments: %d: %s\n",
386                         vtos(w_shotorg),
387                         vtos(trace_endpos),
388                         i,
389                         segmentinfo
390                 );
391                 #endif
392                 te_customflash(trace_endpos, 50, 2, '1 1 0');
393         }
394         #endif
395
396         // if we're bursting, use burst visual effects
397         new_beam_type += burst;
398
399         // network information: beam type
400         if(new_beam_type != self.beam_type)
401         {
402                 self.SendFlags |= 8;
403                 self.beam_type = new_beam_type;
404         }
405
406         self.owner.lg_fire_prev = time;
407         self.nextthink = time;
408 }
409
410 // Attack functions ========================= 
411 void W_Arc_Beam(void)
412 {
413         print("W_Arc_Beam();\n");
414         // only play fire sound if 1 sec has passed since player let go the fire button
415         if(time - self.lg_fire_prev > 1)
416                 sound(self, CH_WEAPON_A, "weapons/lgbeam_fire.wav", VOL_BASE, ATTN_NORM);
417
418         entity beam, oldself;
419
420         self.arc_beam = beam = spawn();
421         beam.classname = "W_Arc_Beam";
422         beam.solid = SOLID_NOT;
423         beam.think = W_Arc_Beam_Think;
424         beam.owner = self;
425         beam.movetype = MOVETYPE_NONE;
426         beam.shot_spread = 1;
427         beam.bot_dodge = TRUE;
428         beam.bot_dodgerating = WEP_CVAR(arc, beam_damage);
429         Net_LinkEntity(beam, FALSE, 0, W_Arc_Beam_Send);
430
431         oldself = self;
432         self = beam;
433         self.think();
434         self = oldself;
435 }
436
437 float W_Arc(float req)
438 {
439         switch(req)
440         {
441                 case WR_AIM:
442                 {
443                         if(WEP_CVAR(arc, beam_botaimspeed))
444                                 self.BUTTON_ATCK = bot_aim(WEP_CVAR(arc, beam_botaimspeed), 0, WEP_CVAR(arc, beam_botaimlifetime), FALSE);
445                         else
446                                 self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE);
447                         return TRUE;
448                 }
449                 case WR_THINK:
450                 {
451                         if(self.BUTTON_ATCK)
452                         {
453                                 if(self.BUTTON_ATCK_prev) // TODO: Find another way to implement this!
454                                         /*if(self.animstate_startframe == self.anim_shoot_x && self.animstate_numframes == self.anim_shoot_y)
455                                                 weapon_thinkf(WFRAME_DONTCHANGE, autocvar_g_balance_arc_primary_animtime, w_ready);
456                                         else*/
457                                                 weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
458                                 
459                                 if(weapon_prepareattack(0, 0))
460                                 {
461                                         if((!self.arc_beam) || wasfreed(self.arc_beam))
462                                                 W_Arc_Beam();
463                                         
464                                         if(!self.BUTTON_ATCK_prev)
465                                         {
466                                                 weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
467                                                 self.BUTTON_ATCK_prev = 1;
468                                         }
469                                 }
470                         } 
471                         else // todo
472                         {
473                                 if(self.BUTTON_ATCK_prev != 0)
474                                 {
475                                         weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
476                                         ATTACK_FINISHED(self) = time + WEP_CVAR(arc, beam_refire) * W_WeaponRateFactor();
477                                 }
478                                 self.BUTTON_ATCK_prev = 0;
479                         }
480
481                         //if(self.BUTTON_ATCK2)
482                                 //if(weapon_prepareattack(1, autocvar_g_balance_arc_secondary_refire))
483                                 //{
484                                 //      W_Arc_Attack2();
485                                 //      self.arc_count = autocvar_g_balance_arc_secondary_count;
486                                 //      weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_arc_secondary_animtime, w_arc_checkattack);
487                                 //      self.arc_secondarytime = time + autocvar_g_balance_arc_secondary_refire2 * W_WeaponRateFactor();
488                                 //}
489                                 
490                         return TRUE;
491                 }
492                 case WR_INIT:
493                 {
494                         precache_model("models/weapons/g_arc.md3");
495                         precache_model("models/weapons/v_arc.md3");
496                         precache_model("models/weapons/h_arc.iqm");
497                         //precache_sound("weapons/arc_bounce.wav");
498                         precache_sound("weapons/arc_fire.wav");
499                         precache_sound("weapons/arc_fire2.wav");
500                         precache_sound("weapons/arc_impact.wav");
501                         //precache_sound("weapons/arc_impact_combo.wav");
502                         //precache_sound("weapons/W_Arc_Beam_fire.wav");
503                         return TRUE;
504                 }
505                 case WR_CHECKAMMO1:
506                 {
507                         return !WEP_CVAR(arc, beam_ammo) || (self.WEP_AMMO(ARC) > 0);
508                 }
509                 case WR_CHECKAMMO2:
510                 {
511                         //return self.WEP_AMMO(ARC) >= WEP_CVAR_SEC(arc, ammo);
512                         return TRUE;
513                 }
514                 case WR_CONFIG:
515                 {
516                         ARC_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS)
517                         return TRUE;
518                 }
519                 case WR_KILLMESSAGE:
520                 {
521                         if(w_deathtype & HITTYPE_SECONDARY)
522                         {
523                                 return WEAPON_ELECTRO_MURDER_ORBS;
524                         }
525                         else
526                         {
527                                 if(w_deathtype & HITTYPE_BOUNCE)
528                                         return WEAPON_ELECTRO_MURDER_COMBO;
529                                 else
530                                         return WEAPON_ELECTRO_MURDER_BOLT;
531                         }
532                 }
533                 case WR_RESETPLAYER:
534                 {
535                         //self.arc_secondarytime = time;
536                         return TRUE;
537                 }
538         }
539         return FALSE;
540 }
541
542 void ArcInit(void)
543 {
544         WEP_ACTION(WEP_ARC, WR_INIT);
545         arc_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 1);
546         arc_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 2);
547         arc_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 3);
548         arc_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 4);
549         ARC_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP)
550 }
551 #endif
552 #ifdef CSQC
553 float W_Arc(float req)
554 {
555         switch(req)
556         {
557                 case WR_IMPACTEFFECT:
558                 {
559                         vector org2;
560                         org2 = w_org + w_backoff * 6;
561                         
562                         if(w_deathtype & HITTYPE_SECONDARY)
563                         {
564                                 pointparticles(particleeffectnum("arc_ballexplode"), org2, '0 0 0', 1);
565                                 if(!w_issilent)
566                                         sound(self, CH_SHOTS, "weapons/arc_impact.wav", VOL_BASE, ATTN_NORM);
567                         }
568                         else
569                         {
570                                 pointparticles(particleeffectnum("arc_impact"), org2, '0 0 0', 1);
571                                 if(!w_issilent)
572                                         sound(self, CH_SHOTS, "weapons/arc_impact.wav", VOL_BASE, ATTN_NORM);
573                         }
574                         
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