]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/warpzonelib/common.qc
change the sense of the transform in findradius
[divverent/nexuiz.git] / data / qcsrc / warpzonelib / common.qc
1 void WarpZone_SetUp(entity e, vector my_org, vector my_ang, vector other_org, vector other_ang)
2 {
3         e.warpzone_transform = AnglesTransform_Divide(other_ang, AnglesTransform_TurnDirectionFR(my_ang));
4         e.warpzone_shift = AnglesTransform_PrePostShift_GetPostShift(my_org, e.warpzone_transform, other_org);
5         e.warpzone_origin = my_org;
6         e.warpzone_targetorigin = other_org;
7         e.warpzone_angles = my_ang;
8         e.warpzone_targetangles = other_ang;
9         fixedmakevectors(my_ang); e.warpzone_forward = v_forward;
10         fixedmakevectors(other_ang); e.warpzone_targetforward = v_forward;
11 }
12
13 .entity enemy;
14
15 vector WarpZoneLib_BoxTouchesBrush_mins;
16 vector WarpZoneLib_BoxTouchesBrush_maxs;
17 entity WarpZoneLib_BoxTouchesBrush_ent;
18 entity WarpZoneLib_BoxTouchesBrush_ignore;
19 float WarpZoneLib_BoxTouchesBrush_Recurse()
20 {
21         float s;
22         entity se;
23         float f;
24
25         tracebox('0 0 0', WarpZoneLib_BoxTouchesBrush_mins, WarpZoneLib_BoxTouchesBrush_maxs, '0 0 0', MOVE_NOMONSTERS, WarpZoneLib_BoxTouchesBrush_ignore);
26 #ifdef CSQC
27         if (trace_networkentity)
28         {
29                 dprint("hit a network ent, cannot continue WarpZoneLib_BoxTouchesBrush\n");
30                 // we cannot continue, as a player blocks us...
31                 // so, abort
32                 return 0;
33         }
34 #endif
35         if not(trace_ent)
36                 return 0;
37         if (trace_ent == WarpZoneLib_BoxTouchesBrush_ent)
38                 return 1;
39
40         se = trace_ent;
41         s = se.solid;
42         se.solid = SOLID_NOT;
43         f = WarpZoneLib_BoxTouchesBrush_Recurse();
44         se.solid = s;
45
46         return f;
47 }
48
49 float WarpZoneLib_BoxTouchesBrush(vector mi, vector ma, entity e, entity ig)
50 {
51     float f, s;
52
53     if not(e.modelindex)
54         return 1;
55
56     s = e.solid;
57     e.solid = SOLID_BSP;
58     WarpZoneLib_BoxTouchesBrush_mins = mi;
59     WarpZoneLib_BoxTouchesBrush_maxs = ma;
60     WarpZoneLib_BoxTouchesBrush_ent = e;
61     WarpZoneLib_BoxTouchesBrush_ignore = ig;
62     f = WarpZoneLib_BoxTouchesBrush_Recurse();
63     e.solid = s;
64
65     return f;
66 }
67
68 entity WarpZone_Find(vector mi, vector ma)
69 {
70         // if we are near any warpzone planes - MOVE AWAY (work around nearclip)
71         entity e;
72         for(e = world; (e = find(e, classname, "trigger_warpzone")); )
73                 if(WarpZoneLib_BoxTouchesBrush(mi, ma, e, world))
74                         return e;
75         return world;
76 }
77
78 void WarpZone_MakeAllSolid()
79 {
80         entity e;
81         for(e = world; (e = find(e, classname, "trigger_warpzone")); )
82                 e.solid = SOLID_BSP;
83 }
84
85 void WarpZone_MakeAllOther()
86 {
87         entity e;
88         for(e = world; (e = find(e, classname, "trigger_warpzone")); )
89                 e.solid = SOLID_TRIGGER;
90 }
91
92 void WarpZone_TraceBox(vector org, vector mi, vector ma, vector end, float nomonsters, entity forent)
93 {
94         float frac, sol;
95         vector o0, e0;
96         entity wz;
97         vector vf, vr, vu;
98         vf = v_forward;
99         vr = v_right;
100         vu = v_up;
101         o0 = org;
102         e0 = end;
103         // if starting in warpzone, first transform
104         wz = WarpZone_Find(org + mi, org + ma);
105         if(wz)
106         {
107                 org = WarpZone_TransformOrigin(wz, trace_endpos);
108                 end = WarpZone_TransformOrigin(wz, end);
109                 WarpZone_trace_velocity = WarpZone_TransformVelocity(wz, WarpZone_trace_velocity);
110                 WarpZone_trace_angles = WarpZone_TransformAngles(wz, WarpZone_trace_angles);
111                 WarpZone_trace_v_angle = WarpZone_TransformVAngles(wz, WarpZone_trace_v_angle);
112         }
113         WarpZone_MakeAllSolid();
114         sol = -1;
115         frac = 0;
116         for(;;)
117         {
118                 tracebox(org, mi, ma, end, nomonsters, forent);
119                 if(WarpZone_trace_callback)
120                         WarpZone_trace_callback(org, trace_endpos, end);
121                 if(sol < 0)
122                         sol = trace_startsolid;
123                 if(trace_fraction >= 1)
124                         break;
125                 frac = trace_fraction = frac + (1 - frac) * trace_fraction;
126                 if(trace_ent.classname != "trigger_warpzone")
127                         break;
128                 if(trace_ent == wz)
129                 {
130                         dprint("I transformed into the same zone again, wtf, aborting the trace\n");
131                         break;
132                 }
133                 wz = trace_ent;
134                 // we hit a warpzone... so, let's perform the trace after the warp again
135                 org = WarpZone_TransformOrigin(wz, trace_endpos);
136                 end = WarpZone_TransformOrigin(wz, end);
137                 WarpZone_trace_velocity = WarpZone_TransformVelocity(wz, WarpZone_trace_velocity);
138                 WarpZone_trace_angles = WarpZone_TransformAngles(wz, WarpZone_trace_angles);
139                 WarpZone_trace_v_angle = WarpZone_TransformVAngles(wz, WarpZone_trace_v_angle);
140         }
141         WarpZone_MakeAllOther();
142         trace_startsolid = sol;
143         WarpZone_trace_endpos = o0 + (e0 - o0) * trace_fraction;
144         v_forward = vf;
145         v_right = vr;
146         v_up = vu;
147 }
148
149 void WarpZone_TraceBox_ThroughZone(vector org, vector mi, vector ma, vector end, float nomonsters, entity forent, entity zone)
150 {
151         float frac, sol;
152         vector o0, e0;
153         entity wz;
154         vector vf, vr, vu;
155         vf = v_forward;
156         vr = v_right;
157         vu = v_up;
158         o0 = org;
159         e0 = end;
160         // if starting in warpzone, first transform
161         wz = WarpZone_Find(org + mi, org + ma);
162         if(wz)
163         {
164                 if(wz != zone)
165                 {
166                         // we are in ANOTHER warpzone. This is bad. Make a zero length trace and return.
167                         sol = 1;
168                         trace_fraction = 0;
169                         trace_endpos = org;
170                         goto fail;
171                 }
172                 org = WarpZone_TransformOrigin(wz, trace_endpos);
173                 end = WarpZone_TransformOrigin(wz, end);
174                 WarpZone_trace_velocity = WarpZone_TransformVelocity(wz, WarpZone_trace_velocity);
175                 WarpZone_trace_angles = WarpZone_TransformAngles(wz, WarpZone_trace_angles);
176                 WarpZone_trace_v_angle = WarpZone_TransformVAngles(wz, WarpZone_trace_v_angle);
177         }
178         WarpZone_MakeAllSolid();
179         sol = -1;
180         frac = 0;
181         for(;;)
182         {
183                 tracebox(org, mi, ma, end, nomonsters, forent);
184                 if(WarpZone_trace_callback)
185                         WarpZone_trace_callback(org, trace_endpos, end);
186                 if(sol < 0)
187                         sol = trace_startsolid;
188                 if(trace_fraction >= 1)
189                         break;
190                 frac = trace_fraction = frac + (1 - frac) * trace_fraction;
191                 if(trace_ent.classname != "trigger_warpzone")
192                         break;
193                 if(trace_ent != zone)
194                         break;
195                 if(trace_ent == wz)
196                 {
197                         dprint("I transformed into the same zone again, wtf, aborting the trace\n");
198                         break;
199                 }
200                 wz = trace_ent;
201                 // we hit a warpzone... so, let's perform the trace after the warp again
202                 org = WarpZone_TransformOrigin(wz, trace_endpos);
203                 end = WarpZone_TransformOrigin(wz, end);
204                 WarpZone_trace_velocity = WarpZone_TransformVelocity(wz, WarpZone_trace_velocity);
205                 WarpZone_trace_angles = WarpZone_TransformAngles(wz, WarpZone_trace_angles);
206                 WarpZone_trace_v_angle = WarpZone_TransformVAngles(wz, WarpZone_trace_v_angle);
207         }
208         WarpZone_MakeAllOther();
209 :fail
210         trace_startsolid = sol;
211         WarpZone_trace_endpos = o0 + (e0 - o0) * trace_fraction;
212         v_forward = vf;
213         v_right = vr;
214         v_up = vu;
215 }
216
217 void WarpZone_TraceLine(vector org, vector end, float nomonsters, entity forent)
218 {
219         WarpZone_TraceBox(org, '0 0 0', '0 0 0', end, nomonsters, forent);
220 }
221
222 void WarpZone_TraceToss(entity e, entity forent)
223 {
224         float g, dt;
225         vector vf, vr, vu, v0, o0;
226         entity wz;
227         vf = v_forward;
228         vr = v_right;
229         vu = v_up;
230         v0 = e.velocity;
231         o0 = e.origin;
232         // if starting in warpzone, first transform
233         wz = WarpZone_Find(e.origin + e.mins, e.origin + e.maxs);
234         if(wz)
235         {
236                 setorigin(e, WarpZone_TransformOrigin(wz, e.origin));
237                 e.velocity = WarpZone_TransformVelocity(wz, e.velocity);
238         }
239         WarpZone_MakeAllSolid();
240         g = cvar("sv_gravity") * e.gravity;
241         WarpZone_tracetoss_time = 0;
242         for(;;)
243         {
244                 tracetoss(e, forent);
245                 if(WarpZone_trace_callback)
246                         WarpZone_trace_callback(e.origin, trace_endpos, trace_endpos);
247                 e.origin = trace_endpos;
248                 dt = vlen(e.origin - o0) / vlen(e.velocity);
249                 WarpZone_tracetoss_time += dt;
250                 e.velocity_z -= WarpZone_tracetoss_time * g;
251                 if(trace_fraction >= 1)
252                         break;
253                 if(trace_ent.classname != "trigger_warpzone")
254                         break;
255                 if(trace_ent == wz)
256                 {
257                         dprint("I transformed into the same zone again, wtf, aborting the trace\n");
258                         break;
259                 }
260                 wz = trace_ent;
261                 // we hit a warpzone... so, let's perform the trace after the warp again
262                 e.origin = WarpZone_TransformOrigin(wz, e.origin);
263                 e.velocity = WarpZone_TransformVelocity(wz, e.velocity);
264         }
265         WarpZone_MakeAllOther();
266         v_forward = vf;
267         v_right = vr;
268         v_up = vu;
269         WarpZone_tracetoss_velocity = e.velocity;
270         e.velocity = v0;
271         e.origin = o0;
272         WarpZone_trace_endpos = e.origin + e.velocity * WarpZone_tracetoss_time;
273         WarpZone_trace_endpos_z -= 0.5 * g * WarpZone_tracetoss_time * WarpZone_tracetoss_time;
274 }
275
276 void WarpZone_TrailParticles(entity own, float eff, vector org, vector end)
277 {
278         vector vf, vr, vu;
279         entity e;
280         vf = v_forward;
281         vr = v_right;
282         vu = v_up;
283         WarpZone_MakeAllSolid();
284         e = world;
285         for(;;)
286         {
287                 traceline(org, end, MOVE_NOMONSTERS, world);
288                 //print(vtos(org), " to ", vtos(trace_endpos), "\n");
289                 trailparticles(own, eff, org, trace_endpos);
290                 if(trace_fraction >= 1)
291                         break;
292                 if(trace_ent.classname != "trigger_warpzone")
293                         break;
294                 // we hit a warpzone... so, let's perform the trace after the warp again
295                 org = WarpZone_TransformOrigin(trace_ent, trace_endpos);
296                 end = WarpZone_TransformOrigin(trace_ent, end);
297                 if(trace_ent == e)
298                 {
299                         dprint("I transformed into the same zone again, wtf, aborting the trace\n");
300                         break;
301                 }
302                 e = trace_ent;
303         }
304         WarpZone_MakeAllOther();
305         v_forward = vf;
306         v_right = vr;
307         v_up = vu;
308 }
309
310 float WarpZone_PlaneDist(entity wz, vector v)
311 {
312         return (v - wz.warpzone_origin) * wz.warpzone_forward;
313 }
314
315 float WarpZone_TargetPlaneDist(entity wz, vector v)
316 {
317         return (v - wz.warpzone_targetorigin) * wz.warpzone_targetforward;
318 }
319
320 vector WarpZone_TransformOrigin(entity wz, vector v)
321 {
322         return wz.warpzone_shift + AnglesTransform_Apply(wz.warpzone_transform, v);
323 }
324
325 vector WarpZone_TransformVelocity(entity wz, vector v)
326 {
327         return AnglesTransform_Apply(wz.warpzone_transform, v);
328 }
329
330 vector WarpZone_TransformAngles(entity wz, vector v)
331 {
332         return AnglesTransform_ApplyToAngles(wz.warpzone_transform, v);
333 }
334
335 vector WarpZone_TransformVAngles(entity wz, vector ang)
336 {
337         float roll;
338
339         roll = ang_z;
340         ang_z = 0;
341
342         ang = AnglesTransform_ApplyToVAngles(wz.warpzone_transform, ang);
343         ang = AnglesTransform_Normalize(ang, TRUE);
344         ang = AnglesTransform_CancelRoll(ang);
345
346         ang_z = roll;
347         return ang;
348 }
349
350 vector WarpZone_UnTransformOrigin(entity wz, vector v)
351 {
352         return AnglesTransform_Apply(AnglesTransform_Invert(wz.warpzone_transform), v - wz.warpzone_shift);
353 }
354
355 vector WarpZone_UnTransformVelocity(entity wz, vector v)
356 {
357         return AnglesTransform_Apply(AnglesTransform_Invert(wz.warpzone_transform), v);
358 }
359
360 vector WarpZone_UnTransformAngles(entity wz, vector v)
361 {
362         return AnglesTransform_ApplyToAngles(AnglesTransform_Invert(wz.warpzone_transform), v);
363 }
364
365 vector WarpZone_UnTransformVAngles(entity wz, vector ang)
366 {
367         float roll;
368
369         roll = ang_z;
370         ang_z = 0;
371
372         ang = AnglesTransform_ApplyToVAngles(AnglesTransform_Invert(wz.warpzone_transform), ang);
373         ang = AnglesTransform_Normalize(ang, TRUE);
374         ang = AnglesTransform_CancelRoll(ang);
375
376         ang_z = roll;
377         return ang;
378 }
379
380 vector WarpZoneLib_NearestPointOnBox(vector mi, vector ma, vector org)
381 {
382         vector nearest;
383         nearest_x = bound(mi_x, org_x, ma_x);
384         nearest_y = bound(mi_y, org_y, ma_y);
385         nearest_z = bound(mi_z, org_z, ma_z);
386         return nearest;
387 }
388
389 .float WarpZone_findradius_hit;
390 .entity WarpZone_findradius_next;
391 void WarpZone_FindRadius_Recurse(vector org, float rad,        vector org0,               vector transform, vector shift, float needlineofsight)
392 //                               blast origin of current search   original blast origin   how to untransform (victim to blast system)
393 {
394         vector org_new;
395         vector org0_new;
396         vector shift_new, transform_new;
397         vector p;
398         entity e, e0;
399         entity wz;
400         if(rad <= 0)
401                 return;
402         e0 = findradius(org, rad);
403         wz = world;
404
405         for(e = e0; e; e = e.chain)
406         {
407                 p = WarpZoneLib_NearestPointOnBox(e.origin + e.mins, e.origin + e.maxs, org0);
408                 if(needlineofsight)
409                 {
410                         traceline(org, p, MOVE_NOMONSTERS, e);
411                         if(trace_fraction < 1)
412                                 continue;
413                 }
414                 if(!e.WarpZone_findradius_hit || vlen(e.WarpZone_findradius_dist) > vlen(org0 - p))
415                 {
416                         e.WarpZone_findradius_nearest = p;
417                         e.WarpZone_findradius_dist = org0 - p;
418                         e.WarpZone_findradius_findorigin = org;
419                         e.WarpZone_findradius_findradius = rad;
420                         if(e.classname == "warpzone_refsys")
421                         {
422                                 // ignore, especially: do not overwrite the refsys parameters
423                         }
424                         else if(e.classname == "trigger_warpzone")
425                         {
426                                 e.WarpZone_findradius_next = wz;
427                                 wz = e;
428                                 e.WarpZone_findradius_hit = 1;
429                                 e.enemy.WarpZone_findradius_dist = '0 0 0'; // we don't want to go through this zone ever again
430                                 e.enemy.WarpZone_findradius_hit = 1;
431                         }
432                         else
433                         {
434                                 e.warpzone_transform = transform;
435                                 e.warpzone_shift = shift;
436                                 e.WarpZone_findradius_hit = 1;
437                         }
438                 }
439         }
440         for(e = wz; e; e = e.WarpZone_findradius_next)
441         {
442                 org0_new = WarpZone_TransformOrigin(e, org);
443                 traceline(e.warpzone_targetorigin, org0_new, MOVE_NOMONSTERS, e);
444                 org_new = trace_endpos;
445
446                 transform_new = AnglesTransform_Multiply(e.warpzone_transform, transform);
447                 shift_new = AnglesTransform_Multiply_GetPostShift(e.warpzone_transform, e.warpzone_shift, transform, shift);
448                 WarpZone_FindRadius_Recurse(
449                         org_new,
450                         bound(0, rad - vlen(org_new - org0_new), rad - 8),
451                         org0_new,
452                         transform_new, shift_new,
453                         needlineofsight);
454                 e.WarpZone_findradius_hit = 0;
455                 e.enemy.WarpZone_findradius_hit = 0;
456         }
457 }
458 entity WarpZone_FindRadius(vector org, float rad, float needlineofsight)
459 {
460         entity e0, e;
461         WarpZone_FindRadius_Recurse(org, rad, org, '0 0 0', '0 0 0', needlineofsight);
462         e0 = findchainfloat(WarpZone_findradius_hit, 1);
463         for(e = e0; e; e = e.chain)
464                 e.WarpZone_findradius_hit = 0;
465         return e0;
466 }
467
468 void WarpZone_Accumulator_Clear(entity acc)
469 {
470         acc.warpzone_transform = '0 0 0';
471         acc.warpzone_shift = '0 0 0';
472 }
473 void WarpZone_Accumulator_AddTransform(entity acc, vector t, vector s)
474 {
475         vector tr, st;
476         tr = AnglesTransform_Multiply(t, acc.warpzone_transform);
477         st = AnglesTransform_Multiply_GetPostShift(t, s, acc.warpzone_transform, acc.warpzone_shift);
478         acc.warpzone_transform = tr;
479         acc.warpzone_shift = st;
480 }
481 void WarpZone_Accumulator_Add(entity acc, entity wz)
482 {
483         vector t, st;
484         t = AnglesTransform_Multiply(wz.warpzone_transform, acc.warpzone_transform);
485         st = AnglesTransform_Multiply_GetPostShift(wz.warpzone_transform, wz.warpzone_shift, acc.warpzone_transform, acc.warpzone_shift);
486         acc.warpzone_transform = t;
487         acc.warpzone_shift = st;
488 }
489
490 .entity WarpZone_refsys;
491 void WarpZone_RefSys_GC()
492 {
493         // garbage collect unused reference systems
494         self.nextthink = time + 1;
495         if(self.owner.WarpZone_refsys != self)
496                 remove(self);
497 }
498 void WarpZone_RefSys_Add(entity me, entity wz)
499 {
500         if(me.WarpZone_refsys.owner != me)
501         {
502                 me.WarpZone_refsys = spawn();
503                 me.WarpZone_refsys.classname = "warpzone_refsys";
504                 me.WarpZone_refsys.owner = me;
505                 me.WarpZone_refsys.think = WarpZone_RefSys_GC;
506                 me.WarpZone_refsys.nextthink = time + 1;
507                 WarpZone_Accumulator_Clear(me.WarpZone_refsys);
508         }
509         if(wz)
510                 WarpZone_Accumulator_Add(me.WarpZone_refsys, wz);
511 }
512 .vector WarpZone_refsys_incremental_shift;
513 .vector WarpZone_refsys_incremental_transform;
514 void WarpZone_RefSys_AddIncrementally(entity me, entity ref)
515 {
516         vector t, s;
517         if(me.WarpZone_refsys_incremental_transform == ref.WarpZone_refsys.warpzone_transform)
518         if(me.WarpZone_refsys_incremental_shift == ref.WarpZone_refsys.warpzone_shift)
519                 return;
520         if(me.WarpZone_refsys.owner != me)
521         {
522                 me.WarpZone_refsys = spawn();
523                 me.WarpZone_refsys.classname = "warpzone_refsys";
524                 me.WarpZone_refsys.owner = me;
525                 me.WarpZone_refsys.think = WarpZone_RefSys_GC;
526                 me.WarpZone_refsys.nextthink = time + 1;
527                 WarpZone_Accumulator_Clear(me.WarpZone_refsys);
528         }
529         t = AnglesTransform_Invert(me.WarpZone_refsys_incremental_transform);
530         s = AnglesTransform_PrePostShift_GetPostShift(me.WarpZone_refsys_incremental_shift, t, '0 0 0');
531         WarpZone_Accumulator_AddTransform(me.WarpZone_refsys, t, s);
532         WarpZone_Accumulator_Add(me.WarpZone_refsys, ref);
533         me.WarpZone_refsys_incremental_shift = ref.WarpZone_refsys.warpzone_shift;
534         me.WarpZone_refsys_incremental_transform = ref.WarpZone_refsys.warpzone_transform;
535 }
536 void WarpZone_RefSys_BeginAddingIncrementally(entity me, entity ref)
537 {
538         me.WarpZone_refsys_incremental_shift = ref.WarpZone_refsys.warpzone_shift;
539         me.WarpZone_refsys_incremental_transform = ref.WarpZone_refsys.warpzone_transform;
540 }
541 vector WarpZone_RefSys_TransformOrigin(entity from, entity to, vector org)
542 {
543         if(from.WarpZone_refsys)
544                 org = WarpZone_UnTransformOrigin(from.WarpZone_refsys, org);
545         if(to.WarpZone_refsys)
546                 org = WarpZone_TransformOrigin(to.WarpZone_refsys, org);
547         return org;
548 }
549 vector WarpZone_RefSys_TransformVelocity(entity from, entity to, vector vel)
550 {
551         if(from.WarpZone_refsys)
552                 vel = WarpZone_UnTransformVelocity(from.WarpZone_refsys, vel);
553         if(to.WarpZone_refsys)
554                 vel = WarpZone_TransformVelocity(to.WarpZone_refsys, vel);
555         return vel;
556 }
557 vector WarpZone_RefSys_TransformAngles(entity from, entity to, vector ang)
558 {
559         if(from.WarpZone_refsys)
560                 ang = WarpZone_UnTransformAngles(from.WarpZone_refsys, ang);
561         if(to.WarpZone_refsys)
562                 ang = WarpZone_TransformAngles(to.WarpZone_refsys, ang);
563         return ang;
564 }
565 vector WarpZone_RefSys_TransformVAngles(entity from, entity to, vector ang)
566 {
567         if(from.WarpZone_refsys)
568                 ang = WarpZone_UnTransformVAngles(from.WarpZone_refsys, ang);
569         if(to.WarpZone_refsys)
570                 ang = WarpZone_TransformVAngles(to.WarpZone_refsys, ang);
571         return ang;
572 }
573 entity WarpZone_RefSys_SpawnSameRefSys(entity me)
574 {
575         entity e;
576         e = spawn();
577         if(me.WarpZone_refsys)
578         {
579                 e.WarpZone_refsys = spawn();
580                 e.WarpZone_refsys.classname = "warpzone_refsys";
581                 e.WarpZone_refsys.owner = e;
582                 e.WarpZone_refsys.think = WarpZone_RefSys_GC;
583                 e.WarpZone_refsys.nextthink = time + 1;
584                 e.WarpZone_refsys.warpzone_shift = me.WarpZone_refsys.warpzone_shift;
585                 e.WarpZone_refsys.warpzone_transform = me.WarpZone_refsys.warpzone_transform;
586         }
587         return e;
588 }