SUB_CalcMove controller is now somewhat working. Now, e.g., lifts will gently acceler...
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / g_subs.qc
1 void SUB_Null() {};
2 float SUB_True() { return 1; }
3 float SUB_False() { return 0; }
4
5 void(vector destangle, float tspeed, void() func) SUB_CalcAngleMove;
6 void()  SUB_CalcMoveDone;
7 void() SUB_CalcAngleMoveDone;
8 //void() SUB_UseTargets;
9 void() SUB_Remove;
10
11 void spawnfunc_info_null (void)
12 {
13         remove(self);
14         // if anything breaks, tell the mapper to fix his map! info_null is meant to remove itself immediately.
15 }
16
17 void setanim(entity e, vector anim, float looping, float override, float restart)
18 {
19         if (anim_x == e.animstate_startframe)
20         if (anim_y == e.animstate_numframes)
21         if (anim_z == e.animstate_framerate)
22         {
23                 if(restart)
24                 {
25                         if(restart > 0)
26                         if(anim_y == 1) // ZYM animation
27                                 BITXOR_ASSIGN(e.effects, EF_RESTARTANIM_BIT);
28                 }
29                 else
30                         return;
31         }
32         e.animstate_startframe = anim_x;
33         e.animstate_numframes = anim_y;
34         e.animstate_framerate = anim_z;
35         e.animstate_starttime = servertime - 0.1 * serverframetime; // shift it a little bit into the past to prevent float inaccuracy hiccups
36         e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
37         e.animstate_looping = looping;
38         e.animstate_override = override;
39         e.frame = e.animstate_startframe;
40         e.frame1time = servertime;
41 };
42
43 void updateanim(entity e)
44 {
45         if (time >= e.animstate_endtime)
46         {
47                 if (e.animstate_looping)
48                 {
49                         e.animstate_starttime = e.animstate_endtime;
50                         e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
51                 }
52                 e.animstate_override = FALSE;
53         }
54         e.frame = e.animstate_startframe + bound(0, (time - e.animstate_starttime) * e.animstate_framerate, e.animstate_numframes - 1);
55         //print(ftos(time), " -> ", ftos(e.frame), "\n");
56 };
57
58 float animparseerror;
59 vector animparseline(float animfile)
60 {
61         local string line;
62         local float c;
63         local vector anim;
64         if (animfile < 0)
65                 return '0 1 2';
66         line = fgets(animfile);
67         c = tokenize_console(line);
68         if (c < 3)
69         {
70                 animparseerror = TRUE;
71                 return '0 1 2';
72         }
73         anim_x = stof(argv(0));
74         anim_y = stof(argv(1));
75         anim_z = stof(argv(2));
76         // don't allow completely bogus values
77         if (anim_x < 0 || anim_y < 1 || anim_z < 0.001)
78                 anim = '0 1 2';
79         return anim;
80 };
81
82 /*
83 ==================
84 SUB_Remove
85
86 Remove self
87 ==================
88 */
89 void SUB_Remove (void)
90 {
91         remove (self);
92 }
93
94 /*
95 ==================
96 SUB_Friction
97
98 Applies some friction to self
99 ==================
100 */
101 .float friction;
102 void SUB_Friction (void)
103 {
104         self.nextthink = time;
105         if(self.flags & FL_ONGROUND)
106                 self.velocity = self.velocity * (1 - frametime * self.friction);
107 }
108
109 /*
110 ==================
111 SUB_VanishOrRemove
112
113 Makes client invisible or removes non-client
114 ==================
115 */
116 void SUB_VanishOrRemove (entity ent)
117 {
118         if (ent.flags & FL_CLIENT)
119         {
120                 // vanish
121                 ent.model = "";
122                 ent.effects = 0;
123                 ent.glow_size = 0;
124                 ent.pflags = 0;
125         }
126         else
127         {
128                 // remove
129                 remove (ent);
130         }
131 }
132
133 void SUB_SetFade_Think (void)
134 {
135         self.think = SUB_SetFade_Think;
136         self.nextthink = self.fade_time;
137         self.alpha = 1 - (time - self.fade_time) * self.fade_rate;
138         if (self.alpha < 0.01)
139                 SUB_VanishOrRemove(self);
140         self.alpha = bound(0.01, self.alpha, 1);
141 }
142
143 /*
144 ==================
145 SUB_SetFade
146
147 Fade 'ent' out when time >= 'when'
148 ==================
149 */
150 void SUB_SetFade (entity ent, float when, float fadetime)
151 {
152         //if (ent.flags & FL_CLIENT) // && ent.deadflag != DEAD_NO)
153         //      return;
154         //ent.alpha = 1;
155         ent.fade_rate = 1/fadetime;
156         ent.fade_time = when;
157         ent.think = SUB_SetFade_Think;
158         ent.nextthink = when;
159 }
160
161 /*
162 =============
163 SUB_CalcMove
164
165 calculate self.velocity and self.nextthink to reach dest from
166 self.origin traveling at speed
167 ===============
168 */
169 void SUB_CalcMoveDone (void)
170 {
171         // After moving, set origin to exact final destination
172
173         setorigin (self, self.finaldest);
174         self.velocity = '0 0 0';
175         self.nextthink = -1;
176         if (self.think1)
177                 self.think1 ();
178 }
179
180 void SUB_CalcMove_controller_think (void)
181 {
182         float movephase;
183         float traveltime;
184         float phasepos;
185         vector delta;
186         vector veloc;
187         if(time < self.animstate_endtime) {
188                 delta = self.finaldest - self.origin;
189                 traveltime = self.animstate_endtime - self.animstate_starttime;
190                 movephase = (time - self.animstate_starttime) / traveltime;
191
192                 //bprint(ftos(movephase));
193                 //bprint("\n");
194
195                 // TODO: Don't mess with the velocity, instead compute where
196                 // we want to be next tick and compute velocity from that
197
198                 veloc = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
199
200                 // scale velocity with pi/2 so integrated over time we
201                 // still have the original velocity
202                 veloc = veloc * 1.5708;
203
204                 // scale velocity with sin(phase)
205                 phasepos = movephase * 3.1416; // phase * pi
206                 phasepos = sin(phasepos);
207                 veloc = veloc * phasepos;       
208
209                 self.owner.velocity = veloc;
210                 self.nextthink = time + 0.1;
211         }
212 }
213
214 void SUB_CalcMove (vector tdest, float tspeed, void() func)
215 {
216         vector  delta;
217         float   traveltime;
218         entity controller;
219
220         if (!tspeed)
221                 objerror ("No speed is defined!");
222
223         self.think1 = func;
224         self.finaldest = tdest;
225         self.think = SUB_CalcMoveDone;
226
227         if (tdest == self.origin)
228         {
229                 self.velocity = '0 0 0';
230                 self.nextthink = self.ltime + 0.1;
231                 return;
232         }
233
234         delta = tdest - self.origin;
235         traveltime = vlen (delta) / tspeed;
236
237         if (traveltime < 0.1)
238         {
239                 self.velocity = '0 0 0';
240                 self.nextthink = self.ltime + 0.1;
241                 return;
242         }
243
244         controller = spawn();
245         controller.classname = "SUB_CalcMove_controller";
246         controller.owner = self;
247         controller.origin = self.origin; // starting point
248         controller.finaldest = tdest; // where do we want to end?
249         controller.animstate_starttime = time;
250         controller.animstate_endtime = time + traveltime;
251         controller.think = SUB_CalcMove_controller_think;
252
253         // let the controller handle the velocity compuation
254         //self.velocity = delta * (1/traveltime);       // QuakeC doesn't allow vector/float division
255         self.nextthink = self.ltime + traveltime;
256         
257         // invoke controller
258         self = controller;
259         self.think();
260 }
261
262 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeed, void() func)
263 {
264         entity  oldself;
265
266         oldself = self;
267         self = ent;
268
269         SUB_CalcMove (tdest, tspeed, func);
270
271         self = oldself;
272 }
273
274 /*
275 =============
276 SUB_CalcAngleMove
277
278 calculate self.avelocity and self.nextthink to reach destangle from
279 self.angles rotating
280
281 The calling function should make sure self.think is valid
282 ===============
283 */
284 void SUB_CalcAngleMoveDone (void)
285 {
286         // After rotating, set angle to exact final angle
287         self.angles = self.finalangle;
288         self.avelocity = '0 0 0';
289         self.nextthink = -1;
290         if (self.think1)
291                 self.think1 ();
292 }
293
294 // FIXME: I fixed this function only for rotation around the main axes
295 void SUB_CalcAngleMove (vector destangle, float tspeed, void() func)
296 {
297         vector  delta;
298         float   traveltime;
299
300         if (!tspeed)
301                 objerror ("No speed is defined!");
302
303         // take the shortest distance for the angles
304         self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
305         self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
306         self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
307         delta = destangle - self.angles;
308         traveltime = vlen (delta) / tspeed;
309
310         self.think1 = func;
311         self.finalangle = destangle;
312         self.think = SUB_CalcAngleMoveDone;
313
314         if (traveltime < 0.1)
315         {
316                 self.avelocity = '0 0 0';
317                 self.nextthink = self.ltime + 0.1;
318                 return;
319         }
320
321         self.avelocity = delta * (1 / traveltime);
322         self.nextthink = self.ltime + traveltime;
323 }
324
325 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeed, void() func)
326 {
327         entity  oldself;
328
329         oldself = self;
330         self = ent;
331
332         SUB_CalcAngleMove (destangle, tspeed, func);
333
334         self = oldself;
335 }
336
337 /*
338 ==================
339 main
340
341 unused but required by the engine
342 ==================
343 */
344 void main (void)
345 {
346
347 }
348
349 // Misc
350
351 /*
352 ==================
353 traceline_antilag
354
355 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
356 Additionally it moves players back into the past before the trace and restores them afterward.
357 ==================
358 */
359 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
360 {
361         local entity player;
362         local float oldsolid;
363
364         // check whether antilagged traces are enabled
365         if (lag < 0.001)
366                 lag = 0;
367         if (clienttype(forent) != CLIENTTYPE_REAL)
368                 lag = 0; // only antilag for clients
369
370         // change shooter to SOLID_BBOX so the shot can hit corpses
371         if(source)
372         {
373                 oldsolid = source.dphitcontentsmask;
374                 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
375         }
376
377         if (lag)
378         {
379                 // take players back into the past
380                 player = player_list;
381                 while (player)
382                 {
383                         antilag_takeback(player, time - lag);
384                         player = player.nextplayer;
385                 }
386         }
387
388         // do the trace
389         if(wz)
390                 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
391         else
392                 tracebox (v1, mi, ma, v2, nomonst, forent);
393
394         // restore players to current positions
395         if (lag)
396         {
397                 player = player_list;
398                 while (player)
399                 {
400                         antilag_restore(player);
401                         player = player.nextplayer;
402                 }
403         }
404
405         // restore shooter solid type
406         if(source)
407                 source.dphitcontentsmask = oldsolid;
408 }
409 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
410 {
411         tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
412 }
413 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
414 {
415         if (cvar("g_antilag") != 2 || source.cvar_cl_noantilag)
416                 lag = 0;
417         traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
418 }
419 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
420 {
421         if (cvar("g_antilag") != 2 || source.cvar_cl_noantilag)
422                 lag = 0;
423         tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
424 }
425 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
426 {
427         tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
428 }
429 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
430 {
431         if (cvar("g_antilag") != 2 || source.cvar_cl_noantilag)
432                 lag = 0;
433         WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
434 }
435 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
436 {
437         if (cvar("g_antilag") != 2 || source.cvar_cl_noantilag)
438                 lag = 0;
439         tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
440 }
441
442 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent) // returns the number of traces done, for benchmarking
443 {
444         vector pos, dir, t;
445         float nudge;
446
447         //nudge = 2 * cvar("collision_impactnudge"); // why not?
448         nudge = 0.5;
449
450         dir = normalize(v2 - v1);
451
452         pos = v1 + dir * nudge;
453
454         float c;
455         c = 0;
456
457         for(;;)
458         {
459                 if((pos - v1) * dir >= (v2 - v1) * dir)
460                 {
461                         // went too far
462                         trace_fraction = 1;
463                         trace_endpos = v2;
464                         return c;
465                 }
466
467                 tracebox(pos, mi, ma, v2, nomonsters, forent);
468                 ++c;
469
470                 if(c == 50)
471                 {
472                         dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
473                         dprint("  Nudging gets us nowhere at ", vtos(pos), "\n");
474                         dprint("  trace_endpos is ", vtos(trace_endpos), "\n");
475                         dprint("  trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
476                 }
477
478                 if(trace_startsolid)
479                 {
480                         // we started inside solid.
481                         // then trace from endpos to pos
482                         t = trace_endpos;
483                         tracebox(t, mi, ma, pos, nomonsters, forent);
484                         ++c;
485                         if(trace_startsolid)
486                         {
487                                 // t is still inside solid? bad
488                                 // force advance, then, and retry
489                                 pos = t + dir * nudge;
490                         }
491                         else
492                         {
493                                 // we actually LEFT solid!
494                                 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
495                                 return c;
496                         }
497                 }
498                 else
499                 {
500                         // pos is outside solid?!? but why?!? never mind, just return it.
501                         trace_endpos = pos;
502                         trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
503                         return c;
504                 }
505         }
506 }
507
508 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent)
509 {
510 #if 0
511         vector pos, dir, t;
512         float nudge;
513
514         //nudge = 2 * cvar("collision_impactnudge"); // why not?
515         nudge = 0.5;
516
517         dir = normalize(v2 - v1);
518
519         pos = v1 + dir * nudge;
520
521         for(;;)
522         {
523                 if((pos - v1) * dir >= (v2 - v1) * dir)
524                 {
525                         // went too far
526                         trace_fraction = 1;
527                         return;
528                 }
529
530                 traceline(pos, v2, nomonsters, forent);
531
532                 if(trace_startsolid)
533                 {
534                         // we started inside solid.
535                         // then trace from endpos to pos
536                         t = trace_endpos;
537                         traceline(t, pos, nomonsters, forent);
538                         if(trace_startsolid)
539                         {
540                                 // t is inside solid? bad
541                                 // force advance, then
542                                 pos = pos + dir * nudge;
543                         }
544                         else
545                         {
546                                 // we actually LEFT solid!
547                                 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
548                                 return;
549                         }
550                 }
551                 else
552                 {
553                         // pos is outside solid?!? but why?!? never mind, just return it.
554                         trace_endpos = pos;
555                         trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
556                         return;
557                 }
558         }
559 #else
560         tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent);
561 }
562
563 /*
564 ==================
565 findbetterlocation
566
567 Returns a point at least 12 units away from walls
568 (useful for explosion animations, although the blast is performed where it really happened)
569 Ripped from DPMod
570 ==================
571 */
572 vector findbetterlocation (vector org, float mindist)
573 {
574         vector  loc;
575         vector vec;
576         float c, h;
577
578         vec = mindist * '1 0 0';
579         c = 0;
580         while (c < 6)
581         {
582                 traceline (org, org + vec, TRUE, world);
583                 vec = vec * -1;
584                 if (trace_fraction < 1)
585                 {
586                         loc = trace_endpos;
587                         traceline (loc, loc + vec, TRUE, world);
588                         if (trace_fraction >= 1)
589                                 org = loc + vec;
590                 }
591                 if (c & 1)
592                 {
593                         h = vec_y;
594                         vec_y = vec_x;
595                         vec_x = vec_z;
596                         vec_z = h;
597                 }
598                 c = c + 1;
599         }
600
601         return org;
602 }
603
604 /*
605 ==================
606 crandom
607
608 Returns a random number between -1.0 and 1.0
609 ==================
610 */
611 float crandom (void)
612 {
613         return 2 * (random () - 0.5);
614 }
615
616 /*
617 ==================
618 Angc used for animations
619 ==================
620 */
621
622
623 float angc (float a1, float a2)
624 {
625         float   a;
626
627         while (a1 > 180)
628                 a1 = a1 - 360;
629         while (a1 < -179)
630                 a1 = a1 + 360;
631
632         while (a2 > 180)
633                 a2 = a2 - 360;
634         while (a2 < -179)
635                 a2 = a2 + 360;
636
637         a = a1 - a2;
638         while (a > 180)
639                 a = a - 360;
640         while (a < -179)
641                 a = a + 360;
642
643         return a;
644 }
645
646 .string lodtarget1;
647 .string lodtarget2;
648 .string lodmodel1;
649 .string lodmodel2;
650 .float lodmodelindex0;
651 .float lodmodelindex1;
652 .float lodmodelindex2;
653 .float loddistance1;
654 .float loddistance2;
655
656 float LOD_customize()
657 {
658         float d;
659
660         if(cvar("loddebug"))
661         {
662                 d = cvar("loddebug");
663                 if(d == 1)
664                         self.modelindex = self.lodmodelindex0;
665                 else if(d == 2 || !self.lodmodelindex2)
666                         self.modelindex = self.lodmodelindex1;
667                 else // if(d == 3)
668                         self.modelindex = self.lodmodelindex2;
669                 return TRUE;
670         }
671
672         // TODO csqc network this so it only gets sent once
673         d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
674         if(d < self.loddistance1)
675                 self.modelindex = self.lodmodelindex0;
676         else if(!self.lodmodelindex2 || d < self.loddistance2)
677                 self.modelindex = self.lodmodelindex1;
678         else
679                 self.modelindex = self.lodmodelindex2;
680
681         return TRUE;
682 }
683
684 void LOD_uncustomize()
685 {
686         self.modelindex = self.lodmodelindex0;
687 }
688
689 void LODmodel_attach()
690 {
691         entity e;
692
693         if(!self.loddistance1)
694                 self.loddistance1 = 1000;
695         if(!self.loddistance2)
696                 self.loddistance2 = 2000;
697         self.lodmodelindex0 = self.modelindex;
698
699         if(self.lodtarget1 != "")
700         {
701                 e = find(world, targetname, self.lodtarget1);
702                 if(e)
703                 {
704                         self.lodmodel1 = e.model;
705                         remove(e);
706                 }
707         }
708         if(self.lodtarget2 != "")
709         {
710                 e = find(world, targetname, self.lodtarget2);
711                 if(e)
712                 {
713                         self.lodmodel2 = e.model;
714                         remove(e);
715                 }
716         }
717
718         if(cvar("loddebug") < 0)
719         {
720                 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
721         }
722
723         if(self.lodmodel1 != "")
724         {
725                 vector mi, ma;
726                 mi = self.mins;
727                 ma = self.maxs;
728
729                 precache_model(self.lodmodel1);
730                 setmodel(self, self.lodmodel1);
731                 self.lodmodelindex1 = self.modelindex;
732
733                 if(self.lodmodel2 != "")
734                 {
735                         precache_model(self.lodmodel2);
736                         setmodel(self, self.lodmodel2);
737                         self.lodmodelindex2 = self.modelindex;
738                 }
739
740                 self.modelindex = self.lodmodelindex0;
741                 setsize(self, mi, ma);
742         }
743
744         if(self.lodmodelindex1)
745                 if not(self.SendEntity)
746                         SetCustomizer(self, LOD_customize, LOD_uncustomize);
747 }
748
749 void SetBrushEntityModel()
750 {
751         if(self.model != "")
752         {
753                 precache_model(self.model);
754                 setmodel(self, self.model); // no precision needed
755                 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
756         }
757         setorigin(self, self.origin);
758         if(self.scale)
759                 setsize(self, self.mins * self.scale, self.maxs * self.scale);
760         else
761                 setsize(self, self.mins, self.maxs);
762 }
763
764 void SetBrushEntityModelNoLOD()
765 {
766         if(self.model != "")
767         {
768                 precache_model(self.model);
769                 setmodel(self, self.model); // no precision needed
770         }
771         setorigin(self, self.origin);
772         if(self.scale)
773                 setsize(self, self.mins * self.scale, self.maxs * self.scale);
774         else
775                 setsize(self, self.mins, self.maxs);
776 }
777
778 /*
779 ================
780 InitTrigger
781 ================
782 */
783
784 void SetMovedir()
785 {
786         if (self.movedir != '0 0 0')
787                 self.movedir = normalize(self.movedir);
788         else
789         {
790                 makevectors (self.angles);
791                 self.movedir = v_forward;
792         }
793
794         self.angles = '0 0 0';
795 };
796
797 void InitTrigger()
798 {
799 // trigger angles are used for one-way touches.  An angle of 0 is assumed
800 // to mean no restrictions, so use a yaw of 360 instead.
801         if (self.movedir == '0 0 0')
802         if (self.angles != '0 0 0')
803                 SetMovedir ();
804         self.solid = SOLID_TRIGGER;
805         SetBrushEntityModel();
806         self.movetype = MOVETYPE_NONE;
807         self.modelindex = 0;
808         self.model = "";
809 };
810
811 void InitSolidBSPTrigger()
812 {
813 // trigger angles are used for one-way touches.  An angle of 0 is assumed
814 // to mean no restrictions, so use a yaw of 360 instead.
815         if (self.movedir == '0 0 0')
816         if (self.angles != '0 0 0')
817                 SetMovedir ();
818         self.solid = SOLID_BSP;
819         SetBrushEntityModel();
820         self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
821 //      self.modelindex = 0;
822         self.model = "";
823 };
824
825 float InitMovingBrushTrigger()
826 {
827 // trigger angles are used for one-way touches.  An angle of 0 is assumed
828 // to mean no restrictions, so use a yaw of 360 instead.
829         self.solid = SOLID_BSP;
830         SetBrushEntityModel();
831         self.movetype = MOVETYPE_PUSH;
832         if(self.modelindex == 0)
833         {
834                 objerror("InitMovingBrushTrigger: no brushes found!");
835                 return 0;
836         }
837         return 1;
838 };