File: game\g_func.c

    1 /*
    2 Copyright (C) 1997-2001 Id Software, Inc.
    3 
    4 This program is free software; you can redistribute it and/or
    5 modify it under the terms of the GNU General Public License
    6 as published by the Free Software Foundation; either version 2
    7 of the License, or (at your option) any later version.
    8 
    9 This program is distributed in the hope that it will be useful,
   10 but WITHOUT ANY WARRANTY; without even the implied warranty of
   11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
   12 
   13 See the GNU General Public License for more details.
   14 
   15 You should have received a copy of the GNU General Public License
   16 along with this program; if not, write to the Free Software
   17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   18 
   19 */
   20 #include "g_local.h"
   21 
   22 /*
   23 =========================================================
   24 
   25   PLATS
   26 
   27   movement options:
   28 
   29   linear
   30   smooth start, hard stop
   31   smooth start, smooth stop
   32 
   33   start
   34   end
   35   acceleration
   36   speed
   37   deceleration
   38   begin sound
   39   end sound
   40   target fired when reaching end
   41   wait at end
   42 
   43   object characteristics that use move segments
   44   ---------------------------------------------
   45   movetype_push, or movetype_stop
   46   action when touched
   47   action when blocked
   48   action when used
   49         disabled?
   50   auto trigger spawning
   51 
   52 
   53 =========================================================
   54 */
   55 
   56 #define PLAT_LOW_TRIGGER        1
   57 
   58 #define STATE_TOP                       0
   59 #define STATE_BOTTOM            1
   60 #define STATE_UP                        2
   61 #define STATE_DOWN                      3
   62 
   63 #define DOOR_START_OPEN         1
   64 #define DOOR_REVERSE            2
   65 #define DOOR_CRUSHER            4
   66 #define DOOR_NOMONSTER          8
   67 #define DOOR_TOGGLE                     32
   68 #define DOOR_X_AXIS                     64
   69 #define DOOR_Y_AXIS                     128
   70 
   71 
   72 //
   73 // Support routines for movement (changes in origin using velocity)
   74 //
   75 
   76 void Move_Done (edict_t *ent)
   77 {
   78         VectorClear (ent->velocity);
   79         ent->moveinfo.endfunc (ent);
   80 }
   81 
   82 void Move_Final (edict_t *ent)
   83 {
   84         if (ent->moveinfo.remaining_distance == 0)
   85         {
   86                 Move_Done (ent);
   87                 return;
   88         }
   89 
   90         VectorScale (ent->moveinfo.dir, ent->moveinfo.remaining_distance / FRAMETIME, ent->velocity);
   91 
   92         ent->think = Move_Done;
   93         ent->nextthink = level.time + FRAMETIME;
   94 }
   95 
   96 void Move_Begin (edict_t *ent)
   97 {
   98         float   frames;
   99 
  100         if ((ent->moveinfo.speed * FRAMETIME) >= ent->moveinfo.remaining_distance)
  101         {
  102                 Move_Final (ent);
  103                 return;
  104         }
  105         VectorScale (ent->moveinfo.dir, ent->moveinfo.speed, ent->velocity);
  106         frames = floor((ent->moveinfo.remaining_distance / ent->moveinfo.speed) / FRAMETIME);
  107         ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * FRAMETIME;
  108         ent->nextthink = level.time + (frames * FRAMETIME);
  109         ent->think = Move_Final;
  110 }
  111 
  112 void Think_AccelMove (edict_t *ent);
  113 
  114 void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*))
  115 {
  116         VectorClear (ent->velocity);
  117         VectorSubtract (dest, ent->s.origin, ent->moveinfo.dir);
  118         ent->moveinfo.remaining_distance = VectorNormalize (ent->moveinfo.dir);
  119         ent->moveinfo.endfunc = func;
  120 
  121         if (ent->moveinfo.speed == ent->moveinfo.accel && ent->moveinfo.speed == ent->moveinfo.decel)
  122         {
  123                 if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent))
  124                 {
  125                         Move_Begin (ent);
  126                 }
  127                 else
  128                 {
  129                         ent->nextthink = level.time + FRAMETIME;
  130                         ent->think = Move_Begin;
  131                 }
  132         }
  133         else
  134         {
  135                 // accelerative
  136                 ent->moveinfo.current_speed = 0;
  137                 ent->think = Think_AccelMove;
  138                 ent->nextthink = level.time + FRAMETIME;
  139         }
  140 }
  141 
  142 
  143 //
  144 // Support routines for angular movement (changes in angle using avelocity)
  145 //
  146 
  147 void AngleMove_Done (edict_t *ent)
  148 {
  149         VectorClear (ent->avelocity);
  150         ent->moveinfo.endfunc (ent);
  151 }
  152 
  153 void AngleMove_Final (edict_t *ent)
  154 {
  155         vec3_t  move;
  156 
  157         if (ent->moveinfo.state == STATE_UP)
  158                 VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, move);
  159         else
  160                 VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, move);
  161 
  162         if (VectorCompare (move, vec3_origin))
  163         {
  164                 AngleMove_Done (ent);
  165                 return;
  166         }
  167 
  168         VectorScale (move, 1.0/FRAMETIME, ent->avelocity);
  169 
  170         ent->think = AngleMove_Done;
  171         ent->nextthink = level.time + FRAMETIME;
  172 }
  173 
  174 void AngleMove_Begin (edict_t *ent)
  175 {
  176         vec3_t  destdelta;
  177         float   len;
  178         float   traveltime;
  179         float   frames;
  180 
  181         // set destdelta to the vector needed to move
  182         if (ent->moveinfo.state == STATE_UP)
  183                 VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, destdelta);
  184         else
  185                 VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, destdelta);
  186         
  187         // calculate length of vector
  188         len = VectorLength (destdelta);
  189         
  190         // divide by speed to get time to reach dest
  191         traveltime = len / ent->moveinfo.speed;
  192 
  193         if (traveltime < FRAMETIME)
  194         {
  195                 AngleMove_Final (ent);
  196                 return;
  197         }
  198 
  199         frames = floor(traveltime / FRAMETIME);
  200 
  201         // scale the destdelta vector by the time spent traveling to get velocity
  202         VectorScale (destdelta, 1.0 / traveltime, ent->avelocity);
  203 
  204         // set nextthink to trigger a think when dest is reached
  205         ent->nextthink = level.time + frames * FRAMETIME;
  206         ent->think = AngleMove_Final;
  207 }
  208 
  209 void AngleMove_Calc (edict_t *ent, void(*func)(edict_t*))
  210 {
  211         VectorClear (ent->avelocity);
  212         ent->moveinfo.endfunc = func;
  213         if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent))
  214         {
  215                 AngleMove_Begin (ent);
  216         }
  217         else
  218         {
  219                 ent->nextthink = level.time + FRAMETIME;
  220                 ent->think = AngleMove_Begin;
  221         }
  222 }
  223 
  224 
  225 /*
  226 ==============
  227 Think_AccelMove
  228 
  229 The team has completed a frame of movement, so
  230 change the speed for the next frame
  231 ==============
  232 */
  233 #define AccelerationDistance(target, rate)      (target * ((target / rate) + 1) / 2)
  234 
  235 void plat_CalcAcceleratedMove(moveinfo_t *moveinfo)
  236 {
  237         float   accel_dist;
  238         float   decel_dist;
  239 
  240         moveinfo->move_speed = moveinfo->speed;
  241 
  242         if (moveinfo->remaining_distance < moveinfo->accel)
  243         {
  244                 moveinfo->current_speed = moveinfo->remaining_distance;
  245                 return;
  246         }
  247 
  248         accel_dist = AccelerationDistance (moveinfo->speed, moveinfo->accel);
  249         decel_dist = AccelerationDistance (moveinfo->speed, moveinfo->decel);
  250 
  251         if ((moveinfo->remaining_distance - accel_dist - decel_dist) < 0)
  252         {
  253                 float   f;
  254 
  255                 f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel);
  256                 moveinfo->move_speed = (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f);
  257                 decel_dist = AccelerationDistance (moveinfo->move_speed, moveinfo->decel);
  258         }
  259 
  260         moveinfo->decel_distance = decel_dist;
  261 };
  262 
  263 void plat_Accelerate (moveinfo_t *moveinfo)
  264 {
  265         // are we decelerating?
  266         if (moveinfo->remaining_distance <= moveinfo->decel_distance)
  267         {
  268                 if (moveinfo->remaining_distance < moveinfo->decel_distance)
  269                 {
  270                         if (moveinfo->next_speed)
  271                         {
  272                                 moveinfo->current_speed = moveinfo->next_speed;
  273                                 moveinfo->next_speed = 0;
  274                                 return;
  275                         }
  276                         if (moveinfo->current_speed > moveinfo->decel)
  277                                 moveinfo->current_speed -= moveinfo->decel;
  278                 }
  279                 return;
  280         }
  281 
  282         // are we at full speed and need to start decelerating during this move?
  283         if (moveinfo->current_speed == moveinfo->move_speed)
  284                 if ((moveinfo->remaining_distance - moveinfo->current_speed) < moveinfo->decel_distance)
  285                 {
  286                         float   p1_distance;
  287                         float   p2_distance;
  288                         float   distance;
  289 
  290                         p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance;
  291                         p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / moveinfo->move_speed));
  292                         distance = p1_distance + p2_distance;
  293                         moveinfo->current_speed = moveinfo->move_speed;
  294                         moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance);
  295                         return;
  296                 }
  297 
  298         // are we accelerating?
  299         if (moveinfo->current_speed < moveinfo->speed)
  300         {
  301                 float   old_speed;
  302                 float   p1_distance;
  303                 float   p1_speed;
  304                 float   p2_distance;
  305                 float   distance;
  306 
  307                 old_speed = moveinfo->current_speed;
  308 
  309                 // figure simple acceleration up to move_speed
  310                 moveinfo->current_speed += moveinfo->accel;
  311                 if (moveinfo->current_speed > moveinfo->speed)
  312                         moveinfo->current_speed = moveinfo->speed;
  313 
  314                 // are we accelerating throughout this entire move?
  315                 if ((moveinfo->remaining_distance - moveinfo->current_speed) >= moveinfo->decel_distance)
  316                         return;
  317 
  318                 // during this move we will accelrate from current_speed to move_speed
  319                 // and cross over the decel_distance; figure the average speed for the
  320                 // entire move
  321                 p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance;
  322                 p1_speed = (old_speed + moveinfo->move_speed) / 2.0;
  323                 p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / p1_speed));
  324                 distance = p1_distance + p2_distance;
  325                 moveinfo->current_speed = (p1_speed * (p1_distance / distance)) + (moveinfo->move_speed * (p2_distance / distance));
  326                 moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance);
  327                 return;
  328         }
  329 
  330         // we are at constant velocity (move_speed)
  331         return;
  332 };
  333 
  334 void Think_AccelMove (edict_t *ent)
  335 {
  336         ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed;
  337 
  338         if (ent->moveinfo.current_speed == 0)           // starting or blocked
  339                 plat_CalcAcceleratedMove(&ent->moveinfo);
  340 
  341         plat_Accelerate (&ent->moveinfo);
  342 
  343         // will the entire move complete on next frame?
  344         if (ent->moveinfo.remaining_distance <= ent->moveinfo.current_speed)
  345         {
  346                 Move_Final (ent);
  347                 return;
  348         }
  349 
  350         VectorScale (ent->moveinfo.dir, ent->moveinfo.current_speed*10, ent->velocity);
  351         ent->nextthink = level.time + FRAMETIME;
  352         ent->think = Think_AccelMove;
  353 }
  354 
  355 
  356 void plat_go_down (edict_t *ent);
  357 
  358 void plat_hit_top (edict_t *ent)
  359 {
  360         if (!(ent->flags & FL_TEAMSLAVE))
  361         {
  362                 if (ent->moveinfo.sound_end)
  363                         gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
  364                 ent->s.sound = 0;
  365         }
  366         ent->moveinfo.state = STATE_TOP;
  367 
  368         ent->think = plat_go_down;
  369         ent->nextthink = level.time + 3;
  370 }
  371 
  372 void plat_hit_bottom (edict_t *ent)
  373 {
  374         if (!(ent->flags & FL_TEAMSLAVE))
  375         {
  376                 if (ent->moveinfo.sound_end)
  377                         gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
  378                 ent->s.sound = 0;
  379         }
  380         ent->moveinfo.state = STATE_BOTTOM;
  381 }
  382 
  383 void plat_go_down (edict_t *ent)
  384 {
  385         if (!(ent->flags & FL_TEAMSLAVE))
  386         {
  387                 if (ent->moveinfo.sound_start)
  388                         gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
  389                 ent->s.sound = ent->moveinfo.sound_middle;
  390         }
  391         ent->moveinfo.state = STATE_DOWN;
  392         Move_Calc (ent, ent->moveinfo.end_origin, plat_hit_bottom);
  393 }
  394 
  395 void plat_go_up (edict_t *ent)
  396 {
  397         if (!(ent->flags & FL_TEAMSLAVE))
  398         {
  399                 if (ent->moveinfo.sound_start)
  400                         gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
  401                 ent->s.sound = ent->moveinfo.sound_middle;
  402         }
  403         ent->moveinfo.state = STATE_UP;
  404         Move_Calc (ent, ent->moveinfo.start_origin, plat_hit_top);
  405 }
  406 
  407 void plat_blocked (edict_t *self, edict_t *other)
  408 {
  409         if (!(other->svflags & SVF_MONSTER) && (!other->client) )
  410         {
  411                 // give it a chance to go away on it's own terms (like gibs)
  412                 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
  413                 // if it's still there, nuke it
  414                 if (other)
  415                         BecomeExplosion1 (other);
  416                 return;
  417         }
  418 
  419         T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
  420 
  421         if (self->moveinfo.state == STATE_UP)
  422                 plat_go_down (self);
  423         else if (self->moveinfo.state == STATE_DOWN)
  424                 plat_go_up (self);
  425 }
  426 
  427 
  428 void Use_Plat (edict_t *ent, edict_t *other, edict_t *activator)
  429 { 
  430         if (ent->think)
  431                 return;         // already down
  432         plat_go_down (ent);
  433 }
  434 
  435 
  436 void Touch_Plat_Center (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
  437 {
  438         if (!other->client)
  439                 return;
  440                 
  441         if (other->health <= 0)
  442                 return;
  443 
  444         ent = ent->enemy;       // now point at the plat, not the trigger
  445         if (ent->moveinfo.state == STATE_BOTTOM)
  446                 plat_go_up (ent);
  447         else if (ent->moveinfo.state == STATE_TOP)
  448                 ent->nextthink = level.time + 1;        // the player is still on the plat, so delay going down
  449 }
  450 
  451 void plat_spawn_inside_trigger (edict_t *ent)
  452 {
  453         edict_t *trigger;
  454         vec3_t  tmin, tmax;
  455 
  456 //
  457 // middle trigger
  458 //      
  459         trigger = G_Spawn();
  460         trigger->touch = Touch_Plat_Center;
  461         trigger->movetype = MOVETYPE_NONE;
  462         trigger->solid = SOLID_TRIGGER;
  463         trigger->enemy = ent;
  464         
  465         tmin[0] = ent->mins[0] + 25;
  466         tmin[1] = ent->mins[1] + 25;
  467         tmin[2] = ent->mins[2];
  468 
  469         tmax[0] = ent->maxs[0] - 25;
  470         tmax[1] = ent->maxs[1] - 25;
  471         tmax[2] = ent->maxs[2] + 8;
  472 
  473         tmin[2] = tmax[2] - (ent->pos1[2] - ent->pos2[2] + st.lip);
  474 
  475         if (ent->spawnflags & PLAT_LOW_TRIGGER)
  476                 tmax[2] = tmin[2] + 8;
  477         
  478         if (tmax[0] - tmin[0] <= 0)
  479         {
  480                 tmin[0] = (ent->mins[0] + ent->maxs[0]) *0.5;
  481                 tmax[0] = tmin[0] + 1;
  482         }
  483         if (tmax[1] - tmin[1] <= 0)
  484         {
  485                 tmin[1] = (ent->mins[1] + ent->maxs[1]) *0.5;
  486                 tmax[1] = tmin[1] + 1;
  487         }
  488         
  489         VectorCopy (tmin, trigger->mins);
  490         VectorCopy (tmax, trigger->maxs);
  491 
  492         gi.linkentity (trigger);
  493 }
  494 
  495 
  496 /*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER
  497 speed   default 150
  498 
  499 Plats are always drawn in the extended position, so they will light correctly.
  500 
  501 If the plat is the target of another trigger or button, it will start out disabled in the extended position until it is trigger, when it will lower and become a normal plat.
  502 
  503 "speed" overrides default 200.
  504 "accel" overrides default 500
  505 "lip"   overrides default 8 pixel lip
  506 
  507 If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determoveinfoned by the model's height.
  508 
  509 Set "sounds" to one of the following:
  510 1) base fast
  511 2) chain slow
  512 */
  513 void SP_func_plat (edict_t *ent)
  514 {
  515         VectorClear (ent->s.angles);
  516         ent->solid = SOLID_BSP;
  517         ent->movetype = MOVETYPE_PUSH;
  518 
  519         gi.setmodel (ent, ent->model);
  520 
  521         ent->blocked = plat_blocked;
  522 
  523         if (!ent->speed)
  524                 ent->speed = 20;
  525         else
  526                 ent->speed *= 0.1;
  527 
  528         if (!ent->accel)
  529                 ent->accel = 5;
  530         else
  531                 ent->accel *= 0.1;
  532 
  533         if (!ent->decel)
  534                 ent->decel = 5;
  535         else
  536                 ent->decel *= 0.1;
  537 
  538         if (!ent->dmg)
  539                 ent->dmg = 2;
  540 
  541         if (!st.lip)
  542                 st.lip = 8;
  543 
  544         // pos1 is the top position, pos2 is the bottom
  545         VectorCopy (ent->s.origin, ent->pos1);
  546         VectorCopy (ent->s.origin, ent->pos2);
  547         if (st.height)
  548                 ent->pos2[2] -= st.height;
  549         else
  550                 ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip;
  551 
  552         ent->use = Use_Plat;
  553 
  554         plat_spawn_inside_trigger (ent);        // the "start moving" trigger   
  555 
  556         if (ent->targetname)
  557         {
  558                 ent->moveinfo.state = STATE_UP;
  559         }
  560         else
  561         {
  562                 VectorCopy (ent->pos2, ent->s.origin);
  563                 gi.linkentity (ent);
  564                 ent->moveinfo.state = STATE_BOTTOM;
  565         }
  566 
  567         ent->moveinfo.speed = ent->speed;
  568         ent->moveinfo.accel = ent->accel;
  569         ent->moveinfo.decel = ent->decel;
  570         ent->moveinfo.wait = ent->wait;
  571         VectorCopy (ent->pos1, ent->moveinfo.start_origin);
  572         VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
  573         VectorCopy (ent->pos2, ent->moveinfo.end_origin);
  574         VectorCopy (ent->s.angles, ent->moveinfo.end_angles);
  575 
  576         ent->moveinfo.sound_start = gi.soundindex ("plats/pt1_strt.wav");
  577         ent->moveinfo.sound_middle = gi.soundindex ("plats/pt1_mid.wav");
  578         ent->moveinfo.sound_end = gi.soundindex ("plats/pt1_end.wav");
  579 }
  580 
  581 //====================================================================
  582 
  583 /*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST
  584 You need to have an origin brush as part of this entity.  The center of that brush will be
  585 the point around which it is rotated. It will rotate around the Z axis by default.  You can
  586 check either the X_AXIS or Y_AXIS box to change that.
  587 
  588 "speed" determines how fast it moves; default value is 100.
  589 "dmg"   damage to inflict when blocked (2 default)
  590 
  591 REVERSE will cause the it to rotate in the opposite direction.
  592 STOP mean it will stop moving instead of pushing entities
  593 */
  594 
  595 void rotating_blocked (edict_t *self, edict_t *other)
  596 {
  597         T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
  598 }
  599 
  600 void rotating_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
  601 {
  602         if (self->avelocity[0] || self->avelocity[1] || self->avelocity[2])
  603                 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
  604 }
  605 
  606 void rotating_use (edict_t *self, edict_t *other, edict_t *activator)
  607 {
  608         if (!VectorCompare (self->avelocity, vec3_origin))
  609         {
  610                 self->s.sound = 0;
  611                 VectorClear (self->avelocity);
  612                 self->touch = NULL;
  613         }
  614         else
  615         {
  616                 self->s.sound = self->moveinfo.sound_middle;
  617                 VectorScale (self->movedir, self->speed, self->avelocity);
  618                 if (self->spawnflags & 16)
  619                         self->touch = rotating_touch;
  620         }
  621 }
  622 
  623 void SP_func_rotating (edict_t *ent)
  624 {
  625         ent->solid = SOLID_BSP;
  626         if (ent->spawnflags & 32)
  627                 ent->movetype = MOVETYPE_STOP;
  628         else
  629                 ent->movetype = MOVETYPE_PUSH;
  630 
  631         // set the axis of rotation
  632         VectorClear(ent->movedir);
  633         if (ent->spawnflags & 4)
  634                 ent->movedir[2] = 1.0;
  635         else if (ent->spawnflags & 8)
  636                 ent->movedir[0] = 1.0;
  637         else // Z_AXIS
  638                 ent->movedir[1] = 1.0;
  639 
  640         // check for reverse rotation
  641         if (ent->spawnflags & 2)
  642                 VectorNegate (ent->movedir, ent->movedir);
  643 
  644         if (!ent->speed)
  645                 ent->speed = 100;
  646         if (!ent->dmg)
  647                 ent->dmg = 2;
  648 
  649 //      ent->moveinfo.sound_middle = "doors/hydro1.wav";
  650 
  651         ent->use = rotating_use;
  652         if (ent->dmg)
  653                 ent->blocked = rotating_blocked;
  654 
  655         if (ent->spawnflags & 1)
  656                 ent->use (ent, NULL, NULL);
  657 
  658         if (ent->spawnflags & 64)
  659                 ent->s.effects |= EF_ANIM_ALL;
  660         if (ent->spawnflags & 128)
  661                 ent->s.effects |= EF_ANIM_ALLFAST;
  662 
  663         gi.setmodel (ent, ent->model);
  664         gi.linkentity (ent);
  665 }
  666 
  667 /*
  668 ======================================================================
  669 
  670 BUTTONS
  671 
  672 ======================================================================
  673 */
  674 
  675 /*QUAKED func_button (0 .5 .8) ?
  676 When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again.
  677 
  678 "angle"         determines the opening direction
  679 "target"        all entities with a matching targetname will be used
  680 "speed"         override the default 40 speed
  681 "wait"          override the default 1 second wait (-1 = never return)
  682 "lip"           override the default 4 pixel lip remaining at end of move
  683 "health"        if set, the button must be killed instead of touched
  684 "sounds"
  685 1) silent
  686 2) steam metal
  687 3) wooden clunk
  688 4) metallic click
  689 5) in-out
  690 */
  691 
  692 void button_done (edict_t *self)
  693 {
  694         self->moveinfo.state = STATE_BOTTOM;
  695         self->s.effects &= ~EF_ANIM23;
  696         self->s.effects |= EF_ANIM01;
  697 }
  698 
  699 void button_return (edict_t *self)
  700 {
  701         self->moveinfo.state = STATE_DOWN;
  702 
  703         Move_Calc (self, self->moveinfo.start_origin, button_done);
  704 
  705         self->s.frame = 0;
  706 
  707         if (self->health)
  708                 self->takedamage = DAMAGE_YES;
  709 }
  710 
  711 void button_wait (edict_t *self)
  712 {
  713         self->moveinfo.state = STATE_TOP;
  714         self->s.effects &= ~EF_ANIM01;
  715         self->s.effects |= EF_ANIM23;
  716 
  717         G_UseTargets (self, self->activator);
  718         self->s.frame = 1;
  719         if (self->moveinfo.wait >= 0)
  720         {
  721                 self->nextthink = level.time + self->moveinfo.wait;
  722                 self->think = button_return;
  723         }
  724 }
  725 
  726 void button_fire (edict_t *self)
  727 {
  728         if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP)
  729                 return;
  730 
  731         self->moveinfo.state = STATE_UP;
  732         if (self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE))
  733                 gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
  734         Move_Calc (self, self->moveinfo.end_origin, button_wait);
  735 }
  736 
  737 void button_use (edict_t *self, edict_t *other, edict_t *activator)
  738 {
  739         self->activator = activator;
  740         button_fire (self);
  741 }
  742 
  743 void button_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
  744 {
  745         if (!other->client)
  746                 return;
  747 
  748         if (other->health <= 0)
  749                 return;
  750 
  751         self->activator = other;
  752         button_fire (self);
  753 }
  754 
  755 void button_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
  756 {
  757         self->activator = attacker;
  758         self->health = self->max_health;
  759         self->takedamage = DAMAGE_NO;
  760         button_fire (self);
  761 }
  762 
  763 void SP_func_button (edict_t *ent)
  764 {
  765         vec3_t  abs_movedir;
  766         float   dist;
  767 
  768         G_SetMovedir (ent->s.angles, ent->movedir);
  769         ent->movetype = MOVETYPE_STOP;
  770         ent->solid = SOLID_BSP;
  771         gi.setmodel (ent, ent->model);
  772 
  773         if (ent->sounds != 1)
  774                 ent->moveinfo.sound_start = gi.soundindex ("switches/butn2.wav");
  775         
  776         if (!ent->speed)
  777                 ent->speed = 40;
  778         if (!ent->accel)
  779                 ent->accel = ent->speed;
  780         if (!ent->decel)
  781                 ent->decel = ent->speed;
  782 
  783         if (!ent->wait)
  784                 ent->wait = 3;
  785         if (!st.lip)
  786                 st.lip = 4;
  787 
  788         VectorCopy (ent->s.origin, ent->pos1);
  789         abs_movedir[0] = fabs(ent->movedir[0]);
  790         abs_movedir[1] = fabs(ent->movedir[1]);
  791         abs_movedir[2] = fabs(ent->movedir[2]);
  792         dist = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip;
  793         VectorMA (ent->pos1, dist, ent->movedir, ent->pos2);
  794 
  795         ent->use = button_use;
  796         ent->s.effects |= EF_ANIM01;
  797 
  798         if (ent->health)
  799         {
  800                 ent->max_health = ent->health;
  801                 ent->die = button_killed;
  802                 ent->takedamage = DAMAGE_YES;
  803         }
  804         else if (! ent->targetname)
  805                 ent->touch = button_touch;
  806 
  807         ent->moveinfo.state = STATE_BOTTOM;
  808 
  809         ent->moveinfo.speed = ent->speed;
  810         ent->moveinfo.accel = ent->accel;
  811         ent->moveinfo.decel = ent->decel;
  812         ent->moveinfo.wait = ent->wait;
  813         VectorCopy (ent->pos1, ent->moveinfo.start_origin);
  814         VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
  815         VectorCopy (ent->pos2, ent->moveinfo.end_origin);
  816         VectorCopy (ent->s.angles, ent->moveinfo.end_angles);
  817 
  818         gi.linkentity (ent);
  819 }
  820 
  821 /*
  822 ======================================================================
  823 
  824 DOORS
  825 
  826   spawn a trigger surrounding the entire team unless it is
  827   already targeted by another
  828 
  829 ======================================================================
  830 */
  831 
  832 /*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER NOMONSTER ANIMATED TOGGLE ANIMATED_FAST
  833 TOGGLE          wait in both the start and end states for a trigger event.
  834 START_OPEN      the door to moves to its destination when spawned, and operate in reverse.  It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors).
  835 NOMONSTER       monsters will not trigger this door
  836 
  837 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
  838 "angle"         determines the opening direction
  839 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
  840 "health"        if set, door must be shot open
  841 "speed"         movement speed (100 default)
  842 "wait"          wait before returning (3 default, -1 = never return)
  843 "lip"           lip remaining at end of move (8 default)
  844 "dmg"           damage to inflict when blocked (2 default)
  845 "sounds"
  846 1)      silent
  847 2)      light
  848 3)      medium
  849 4)      heavy
  850 */
  851 
  852 void door_use_areaportals (edict_t *self, qboolean open)
  853 {
  854         edict_t *t = NULL;
  855 
  856         if (!self->target)
  857                 return;
  858 
  859         while ((t = G_Find (t, FOFS(targetname), self->target)))
  860         {
  861                 if (Q_stricmp(t->classname, "func_areaportal") == 0)
  862                 {
  863                         gi.SetAreaPortalState (t->style, open);
  864                 }
  865         }
  866 }
  867 
  868 void door_go_down (edict_t *self);
  869 
  870 void door_hit_top (edict_t *self)
  871 {
  872         if (!(self->flags & FL_TEAMSLAVE))
  873         {
  874                 if (self->moveinfo.sound_end)
  875                         gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
  876                 self->s.sound = 0;
  877         }
  878         self->moveinfo.state = STATE_TOP;
  879         if (self->spawnflags & DOOR_TOGGLE)
  880                 return;
  881         if (self->moveinfo.wait >= 0)
  882         {
  883                 self->think = door_go_down;
  884                 self->nextthink = level.time + self->moveinfo.wait;
  885         }
  886 }
  887 
  888 void door_hit_bottom (edict_t *self)
  889 {
  890         if (!(self->flags & FL_TEAMSLAVE))
  891         {
  892                 if (self->moveinfo.sound_end)
  893                         gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
  894                 self->s.sound = 0;
  895         }
  896         self->moveinfo.state = STATE_BOTTOM;
  897         door_use_areaportals (self, false);
  898 }
  899 
  900 void door_go_down (edict_t *self)
  901 {
  902         if (!(self->flags & FL_TEAMSLAVE))
  903         {
  904                 if (self->moveinfo.sound_start)
  905                         gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
  906                 self->s.sound = self->moveinfo.sound_middle;
  907         }
  908         if (self->max_health)
  909         {
  910                 self->takedamage = DAMAGE_YES;
  911                 self->health = self->max_health;
  912         }
  913         
  914         self->moveinfo.state = STATE_DOWN;
  915         if (strcmp(self->classname, "func_door") == 0)
  916                 Move_Calc (self, self->moveinfo.start_origin, door_hit_bottom);
  917         else if (strcmp(self->classname, "func_door_rotating") == 0)
  918                 AngleMove_Calc (self, door_hit_bottom);
  919 }
  920 
  921 void door_go_up (edict_t *self, edict_t *activator)
  922 {
  923         if (self->moveinfo.state == STATE_UP)
  924                 return;         // already going up
  925 
  926         if (self->moveinfo.state == STATE_TOP)
  927         {       // reset top wait time
  928                 if (self->moveinfo.wait >= 0)
  929                         self->nextthink = level.time + self->moveinfo.wait;
  930                 return;
  931         }
  932         
  933         if (!(self->flags & FL_TEAMSLAVE))
  934         {
  935                 if (self->moveinfo.sound_start)
  936                         gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
  937                 self->s.sound = self->moveinfo.sound_middle;
  938         }
  939         self->moveinfo.state = STATE_UP;
  940         if (strcmp(self->classname, "func_door") == 0)
  941                 Move_Calc (self, self->moveinfo.end_origin, door_hit_top);
  942         else if (strcmp(self->classname, "func_door_rotating") == 0)
  943                 AngleMove_Calc (self, door_hit_top);
  944 
  945         G_UseTargets (self, activator);
  946         door_use_areaportals (self, true);
  947 }
  948 
  949 void door_use (edict_t *self, edict_t *other, edict_t *activator)
  950 {
  951         edict_t *ent;
  952 
  953         if (self->flags & FL_TEAMSLAVE)
  954                 return;
  955 
  956         if (self->spawnflags & DOOR_TOGGLE)
  957         {
  958                 if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP)
  959                 {
  960                         // trigger all paired doors
  961                         for (ent = self ; ent ; ent = ent->teamchain)
  962                         {
  963                                 ent->message = NULL;
  964                                 ent->touch = NULL;
  965                                 door_go_down (ent);
  966                         }
  967                         return;
  968                 }
  969         }
  970         
  971         // trigger all paired doors
  972         for (ent = self ; ent ; ent = ent->teamchain)
  973         {
  974                 ent->message = NULL;
  975                 ent->touch = NULL;
  976                 door_go_up (ent, activator);
  977         }
  978 };
  979 
  980 void Touch_DoorTrigger (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
  981 {
  982         if (other->health <= 0)
  983                 return;
  984 
  985         if (!(other->svflags & SVF_MONSTER) && (!other->client))
  986                 return;
  987 
  988         if ((self->owner->spawnflags & DOOR_NOMONSTER) && (other->svflags & SVF_MONSTER))
  989                 return;
  990 
  991         if (level.time < self->touch_debounce_time)
  992                 return;
  993         self->touch_debounce_time = level.time + 1.0;
  994 
  995         door_use (self->owner, other, other);
  996 }
  997 
  998 void Think_CalcMoveSpeed (edict_t *self)
  999 {
 1000         edict_t *ent;
 1001         float   min;
 1002         float   time;
 1003         float   newspeed;
 1004         float   ratio;
 1005         float   dist;
 1006 
 1007         if (self->flags & FL_TEAMSLAVE)
 1008                 return;         // only the team master does this
 1009 
 1010         // find the smallest distance any member of the team will be moving
 1011         min = fabs(self->moveinfo.distance);
 1012         for (ent = self->teamchain; ent; ent = ent->teamchain)
 1013         {
 1014                 dist = fabs(ent->moveinfo.distance);
 1015                 if (dist < min)
 1016                         min = dist;
 1017         }
 1018 
 1019         time = min / self->moveinfo.speed;
 1020 
 1021         // adjust speeds so they will all complete at the same time
 1022         for (ent = self; ent; ent = ent->teamchain)
 1023         {
 1024                 newspeed = fabs(ent->moveinfo.distance) / time;
 1025                 ratio = newspeed / ent->moveinfo.speed;
 1026                 if (ent->moveinfo.accel == ent->moveinfo.speed)
 1027                         ent->moveinfo.accel = newspeed;
 1028                 else
 1029                         ent->moveinfo.accel *= ratio;
 1030                 if (ent->moveinfo.decel == ent->moveinfo.speed)
 1031                         ent->moveinfo.decel = newspeed;
 1032                 else
 1033                         ent->moveinfo.decel *= ratio;
 1034                 ent->moveinfo.speed = newspeed;
 1035         }
 1036 }
 1037 
 1038 void Think_SpawnDoorTrigger (edict_t *ent)
 1039 {
 1040         edict_t         *other;
 1041         vec3_t          mins, maxs;
 1042 
 1043         if (ent->flags & FL_TEAMSLAVE)
 1044                 return;         // only the team leader spawns a trigger
 1045 
 1046         VectorCopy (ent->absmin, mins);
 1047         VectorCopy (ent->absmax, maxs);
 1048 
 1049         for (other = ent->teamchain ; other ; other=other->teamchain)
 1050         {
 1051                 AddPointToBounds (other->absmin, mins, maxs);
 1052                 AddPointToBounds (other->absmax, mins, maxs);
 1053         }
 1054 
 1055         // expand 
 1056         mins[0] -= 60;
 1057         mins[1] -= 60;
 1058         maxs[0] += 60;
 1059         maxs[1] += 60;
 1060 
 1061         other = G_Spawn ();
 1062         VectorCopy (mins, other->mins);
 1063         VectorCopy (maxs, other->maxs);
 1064         other->owner = ent;
 1065         other->solid = SOLID_TRIGGER;
 1066         other->movetype = MOVETYPE_NONE;
 1067         other->touch = Touch_DoorTrigger;
 1068         gi.linkentity (other);
 1069 
 1070         if (ent->spawnflags & DOOR_START_OPEN)
 1071                 door_use_areaportals (ent, true);
 1072 
 1073         Think_CalcMoveSpeed (ent);
 1074 }
 1075 
 1076 void door_blocked  (edict_t *self, edict_t *other)
 1077 {
 1078         edict_t *ent;
 1079 
 1080         if (!(other->svflags & SVF_MONSTER) && (!other->client) )
 1081         {
 1082                 // give it a chance to go away on it's own terms (like gibs)
 1083                 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
 1084                 // if it's still there, nuke it
 1085                 if (other)
 1086                         BecomeExplosion1 (other);
 1087                 return;
 1088         }
 1089 
 1090         T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
 1091 
 1092         if (self->spawnflags & DOOR_CRUSHER)
 1093                 return;
 1094 
 1095 
 1096 // if a door has a negative wait, it would never come back if blocked,
 1097 // so let it just squash the object to death real fast
 1098         if (self->moveinfo.wait >= 0)
 1099         {
 1100                 if (self->moveinfo.state == STATE_DOWN)
 1101                 {
 1102                         for (ent = self->teammaster ; ent ; ent = ent->teamchain)
 1103                                 door_go_up (ent, ent->activator);
 1104                 }
 1105                 else
 1106                 {
 1107                         for (ent = self->teammaster ; ent ; ent = ent->teamchain)
 1108                                 door_go_down (ent);
 1109                 }
 1110         }
 1111 }
 1112 
 1113 void door_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
 1114 {
 1115         edict_t *ent;
 1116 
 1117         for (ent = self->teammaster ; ent ; ent = ent->teamchain)
 1118         {
 1119                 ent->health = ent->max_health;
 1120                 ent->takedamage = DAMAGE_NO;
 1121         }
 1122         door_use (self->teammaster, attacker, attacker);
 1123 }
 1124 
 1125 void door_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
 1126 {
 1127         if (!other->client)
 1128                 return;
 1129 
 1130         if (level.time < self->touch_debounce_time)
 1131                 return;
 1132         self->touch_debounce_time = level.time + 5.0;
 1133 
 1134         gi.centerprintf (other, "%s", self->message);
 1135         gi.sound (other, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
 1136 }
 1137 
 1138 void SP_func_door (edict_t *ent)
 1139 {
 1140         vec3_t  abs_movedir;
 1141 
 1142         if (ent->sounds != 1)
 1143         {
 1144                 ent->moveinfo.sound_start = gi.soundindex  ("doors/dr1_strt.wav");
 1145                 ent->moveinfo.sound_middle = gi.soundindex  ("doors/dr1_mid.wav");
 1146                 ent->moveinfo.sound_end = gi.soundindex  ("doors/dr1_end.wav");
 1147         }
 1148 
 1149         G_SetMovedir (ent->s.angles, ent->movedir);
 1150         ent->movetype = MOVETYPE_PUSH;
 1151         ent->solid = SOLID_BSP;
 1152         gi.setmodel (ent, ent->model);
 1153 
 1154         ent->blocked = door_blocked;
 1155         ent->use = door_use;
 1156         
 1157         if (!ent->speed)
 1158                 ent->speed = 100;
 1159         if (deathmatch->value)
 1160                 ent->speed *= 2;
 1161 
 1162         if (!ent->accel)
 1163                 ent->accel = ent->speed;
 1164         if (!ent->decel)
 1165                 ent->decel = ent->speed;
 1166 
 1167         if (!ent->wait)
 1168                 ent->wait = 3;
 1169         if (!st.lip)
 1170                 st.lip = 8;
 1171         if (!ent->dmg)
 1172                 ent->dmg = 2;
 1173 
 1174         // calculate second position
 1175         VectorCopy (ent->s.origin, ent->pos1);
 1176         abs_movedir[0] = fabs(ent->movedir[0]);
 1177         abs_movedir[1] = fabs(ent->movedir[1]);
 1178         abs_movedir[2] = fabs(ent->movedir[2]);
 1179         ent->moveinfo.distance = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip;
 1180         VectorMA (ent->pos1, ent->moveinfo.distance, ent->movedir, ent->pos2);
 1181 
 1182         // if it starts open, switch the positions
 1183         if (ent->spawnflags & DOOR_START_OPEN)
 1184         {
 1185                 VectorCopy (ent->pos2, ent->s.origin);
 1186                 VectorCopy (ent->pos1, ent->pos2);
 1187                 VectorCopy (ent->s.origin, ent->pos1);
 1188         }
 1189 
 1190         ent->moveinfo.state = STATE_BOTTOM;
 1191 
 1192         if (ent->health)
 1193         {
 1194                 ent->takedamage = DAMAGE_YES;
 1195                 ent->die = door_killed;
 1196                 ent->max_health = ent->health;
 1197         }
 1198         else if (ent->targetname && ent->message)
 1199         {
 1200                 gi.soundindex ("misc/talk.wav");
 1201                 ent->touch = door_touch;
 1202         }
 1203         
 1204         ent->moveinfo.speed = ent->speed;
 1205         ent->moveinfo.accel = ent->accel;
 1206         ent->moveinfo.decel = ent->decel;
 1207         ent->moveinfo.wait = ent->wait;
 1208         VectorCopy (ent->pos1, ent->moveinfo.start_origin);
 1209         VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
 1210         VectorCopy (ent->pos2, ent->moveinfo.end_origin);
 1211         VectorCopy (ent->s.angles, ent->moveinfo.end_angles);
 1212 
 1213         if (ent->spawnflags & 16)
 1214                 ent->s.effects |= EF_ANIM_ALL;
 1215         if (ent->spawnflags & 64)
 1216                 ent->s.effects |= EF_ANIM_ALLFAST;
 1217 
 1218         // to simplify logic elsewhere, make non-teamed doors into a team of one
 1219         if (!ent->team)
 1220                 ent->teammaster = ent;
 1221 
 1222         gi.linkentity (ent);
 1223 
 1224         ent->nextthink = level.time + FRAMETIME;
 1225         if (ent->health || ent->targetname)
 1226                 ent->think = Think_CalcMoveSpeed;
 1227         else
 1228                 ent->think = Think_SpawnDoorTrigger;
 1229 }
 1230 
 1231 
 1232 /*QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER NOMONSTER ANIMATED TOGGLE X_AXIS Y_AXIS
 1233 TOGGLE causes the door to wait in both the start and end states for a trigger event.
 1234 
 1235 START_OPEN      the door to moves to its destination when spawned, and operate in reverse.  It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors).
 1236 NOMONSTER       monsters will not trigger this door
 1237 
 1238 You need to have an origin brush as part of this entity.  The center of that brush will be
 1239 the point around which it is rotated. It will rotate around the Z axis by default.  You can
 1240 check either the X_AXIS or Y_AXIS box to change that.
 1241 
 1242 "distance" is how many degrees the door will be rotated.
 1243 "speed" determines how fast the door moves; default value is 100.
 1244 
 1245 REVERSE will cause the door to rotate in the opposite direction.
 1246 
 1247 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
 1248 "angle"         determines the opening direction
 1249 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
 1250 "health"        if set, door must be shot open
 1251 "speed"         movement speed (100 default)
 1252 "wait"          wait before returning (3 default, -1 = never return)
 1253 "dmg"           damage to inflict when blocked (2 default)
 1254 "sounds"
 1255 1)      silent
 1256 2)      light
 1257 3)      medium
 1258 4)      heavy
 1259 */
 1260 
 1261 void SP_func_door_rotating (edict_t *ent)
 1262 {
 1263         VectorClear (ent->s.angles);
 1264 
 1265         // set the axis of rotation
 1266         VectorClear(ent->movedir);
 1267         if (ent->spawnflags & DOOR_X_AXIS)
 1268                 ent->movedir[2] = 1.0;
 1269         else if (ent->spawnflags & DOOR_Y_AXIS)
 1270                 ent->movedir[0] = 1.0;
 1271         else // Z_AXIS
 1272                 ent->movedir[1] = 1.0;
 1273 
 1274         // check for reverse rotation
 1275         if (ent->spawnflags & DOOR_REVERSE)
 1276                 VectorNegate (ent->movedir, ent->movedir);
 1277 
 1278         if (!st.distance)
 1279         {
 1280                 gi.dprintf("%s at %s with no distance set\n", ent->classname, vtos(ent->s.origin));
 1281                 st.distance = 90;
 1282         }
 1283 
 1284         VectorCopy (ent->s.angles, ent->pos1);
 1285         VectorMA (ent->s.angles, st.distance, ent->movedir, ent->pos2);
 1286         ent->moveinfo.distance = st.distance;
 1287 
 1288         ent->movetype = MOVETYPE_PUSH;
 1289         ent->solid = SOLID_BSP;
 1290         gi.setmodel (ent, ent->model);
 1291 
 1292         ent->blocked = door_blocked;
 1293         ent->use = door_use;
 1294 
 1295         if (!ent->speed)
 1296                 ent->speed = 100;
 1297         if (!ent->accel)
 1298                 ent->accel = ent->speed;
 1299         if (!ent->decel)
 1300                 ent->decel = ent->speed;
 1301 
 1302         if (!ent->wait)
 1303                 ent->wait = 3;
 1304         if (!ent->dmg)
 1305                 ent->dmg = 2;
 1306 
 1307         if (ent->sounds != 1)
 1308         {
 1309                 ent->moveinfo.sound_start = gi.soundindex  ("doors/dr1_strt.wav");
 1310                 ent->moveinfo.sound_middle = gi.soundindex  ("doors/dr1_mid.wav");
 1311                 ent->moveinfo.sound_end = gi.soundindex  ("doors/dr1_end.wav");
 1312         }
 1313 
 1314         // if it starts open, switch the positions
 1315         if (ent->spawnflags & DOOR_START_OPEN)
 1316         {
 1317                 VectorCopy (ent->pos2, ent->s.angles);
 1318                 VectorCopy (ent->pos1, ent->pos2);
 1319                 VectorCopy (ent->s.angles, ent->pos1);
 1320                 VectorNegate (ent->movedir, ent->movedir);
 1321         }
 1322 
 1323         if (ent->health)
 1324         {
 1325                 ent->takedamage = DAMAGE_YES;
 1326                 ent->die = door_killed;
 1327                 ent->max_health = ent->health;
 1328         }
 1329         
 1330         if (ent->targetname && ent->message)
 1331         {
 1332                 gi.soundindex ("misc/talk.wav");
 1333                 ent->touch = door_touch;
 1334         }
 1335 
 1336         ent->moveinfo.state = STATE_BOTTOM;
 1337         ent->moveinfo.speed = ent->speed;
 1338         ent->moveinfo.accel = ent->accel;
 1339         ent->moveinfo.decel = ent->decel;
 1340         ent->moveinfo.wait = ent->wait;
 1341         VectorCopy (ent->s.origin, ent->moveinfo.start_origin);
 1342         VectorCopy (ent->pos1, ent->moveinfo.start_angles);
 1343         VectorCopy (ent->s.origin, ent->moveinfo.end_origin);
 1344         VectorCopy (ent->pos2, ent->moveinfo.end_angles);
 1345 
 1346         if (ent->spawnflags & 16)
 1347                 ent->s.effects |= EF_ANIM_ALL;
 1348 
 1349         // to simplify logic elsewhere, make non-teamed doors into a team of one
 1350         if (!ent->team)
 1351                 ent->teammaster = ent;
 1352 
 1353         gi.linkentity (ent);
 1354 
 1355         ent->nextthink = level.time + FRAMETIME;
 1356         if (ent->health || ent->targetname)
 1357                 ent->think = Think_CalcMoveSpeed;
 1358         else
 1359                 ent->think = Think_SpawnDoorTrigger;
 1360 }
 1361 
 1362 
 1363 /*QUAKED func_water (0 .5 .8) ? START_OPEN
 1364 func_water is a moveable water brush.  It must be targeted to operate.  Use a non-water texture at your own risk.
 1365 
 1366 START_OPEN causes the water to move to its destination when spawned and operate in reverse.
 1367 
 1368 "angle"         determines the opening direction (up or down only)
 1369 "speed"         movement speed (25 default)
 1370 "wait"          wait before returning (-1 default, -1 = TOGGLE)
 1371 "lip"           lip remaining at end of move (0 default)
 1372 "sounds"        (yes, these need to be changed)
 1373 0)      no sound
 1374 1)      water
 1375 2)      lava
 1376 */
 1377 
 1378 void SP_func_water (edict_t *self)
 1379 {
 1380         vec3_t  abs_movedir;
 1381 
 1382         G_SetMovedir (self->s.angles, self->movedir);
 1383         self->movetype = MOVETYPE_PUSH;
 1384         self->solid = SOLID_BSP;
 1385         gi.setmodel (self, self->model);
 1386 
 1387         switch (self->sounds)
 1388         {
 1389                 default:
 1390                         break;
 1391 
 1392                 case 1: // water
 1393                         self->moveinfo.sound_start = gi.soundindex  ("world/mov_watr.wav");
 1394                         self->moveinfo.sound_end = gi.soundindex  ("world/stp_watr.wav");
 1395                         break;
 1396 
 1397                 case 2: // lava
 1398                         self->moveinfo.sound_start = gi.soundindex  ("world/mov_watr.wav");
 1399                         self->moveinfo.sound_end = gi.soundindex  ("world/stp_watr.wav");
 1400                         break;
 1401         }
 1402 
 1403         // calculate second position
 1404         VectorCopy (self->s.origin, self->pos1);
 1405         abs_movedir[0] = fabs(self->movedir[0]);
 1406         abs_movedir[1] = fabs(self->movedir[1]);
 1407         abs_movedir[2] = fabs(self->movedir[2]);
 1408         self->moveinfo.distance = abs_movedir[0] * self->size[0] + abs_movedir[1] * self->size[1] + abs_movedir[2] * self->size[2] - st.lip;
 1409         VectorMA (self->pos1, self->moveinfo.distance, self->movedir, self->pos2);
 1410 
 1411         // if it starts open, switch the positions
 1412         if (self->spawnflags & DOOR_START_OPEN)
 1413         {
 1414                 VectorCopy (self->pos2, self->s.origin);
 1415                 VectorCopy (self->pos1, self->pos2);
 1416                 VectorCopy (self->s.origin, self->pos1);
 1417         }
 1418 
 1419         VectorCopy (self->pos1, self->moveinfo.start_origin);
 1420         VectorCopy (self->s.angles, self->moveinfo.start_angles);
 1421         VectorCopy (self->pos2, self->moveinfo.end_origin);
 1422         VectorCopy (self->s.angles, self->moveinfo.end_angles);
 1423 
 1424         self->moveinfo.state = STATE_BOTTOM;
 1425 
 1426         if (!self->speed)
 1427                 self->speed = 25;
 1428         self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed;
 1429 
 1430         if (!self->wait)
 1431                 self->wait = -1;
 1432         self->moveinfo.wait = self->wait;
 1433 
 1434         self->use = door_use;
 1435 
 1436         if (self->wait == -1)
 1437                 self->spawnflags |= DOOR_TOGGLE;
 1438 
 1439         self->classname = "func_door";
 1440 
 1441         gi.linkentity (self);
 1442 }
 1443 
 1444 
 1445 #define TRAIN_START_ON          1
 1446 #define TRAIN_TOGGLE            2
 1447 #define TRAIN_BLOCK_STOPS       4
 1448 
 1449 /*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS
 1450 Trains are moving platforms that players can ride.
 1451 The targets origin specifies the min point of the train at each corner.
 1452 The train spawns at the first target it is pointing at.
 1453 If the train is the target of a button or trigger, it will not begin moving until activated.
 1454 speed   default 100
 1455 dmg             default 2
 1456 noise   looping sound to play when the train is in motion
 1457 
 1458 */
 1459 void train_next (edict_t *self);
 1460 
 1461 void train_blocked (edict_t *self, edict_t *other)
 1462 {
 1463         if (!(other->svflags & SVF_MONSTER) && (!other->client) )
 1464         {
 1465                 // give it a chance to go away on it's own terms (like gibs)
 1466                 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
 1467                 // if it's still there, nuke it
 1468                 if (other)
 1469                         BecomeExplosion1 (other);
 1470                 return;
 1471         }
 1472 
 1473         if (level.time < self->touch_debounce_time)
 1474                 return;
 1475 
 1476         if (!self->dmg)
 1477                 return;
 1478         self->touch_debounce_time = level.time + 0.5;
 1479         T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
 1480 }
 1481 
 1482 void train_wait (edict_t *self)
 1483 {
 1484         if (self->target_ent->pathtarget)
 1485         {
 1486                 char    *savetarget;
 1487                 edict_t *ent;
 1488 
 1489                 ent = self->target_ent;
 1490                 savetarget = ent->target;
 1491                 ent->target = ent->pathtarget;
 1492                 G_UseTargets (ent, self->activator);
 1493                 ent->target = savetarget;
 1494 
 1495                 // make sure we didn't get killed by a killtarget
 1496                 if (!self->inuse)
 1497                         return;
 1498         }
 1499 
 1500         if (self->moveinfo.wait)
 1501         {
 1502                 if (self->moveinfo.wait > 0)
 1503                 {
 1504                         self->nextthink = level.time + self->moveinfo.wait;
 1505                         self->think = train_next;
 1506                 }
 1507                 else if (self->spawnflags & TRAIN_TOGGLE)  // && wait < 0
 1508                 {
 1509                         train_next (self);
 1510                         self->spawnflags &= ~TRAIN_START_ON;
 1511                         VectorClear (self->velocity);
 1512                         self->nextthink = 0;
 1513                 }
 1514 
 1515                 if (!(self->flags & FL_TEAMSLAVE))
 1516                 {
 1517                         if (self->moveinfo.sound_end)
 1518                                 gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
 1519                         self->s.sound = 0;
 1520                 }
 1521         }
 1522         else
 1523         {
 1524                 train_next (self);
 1525         }
 1526         
 1527 }
 1528 
 1529 void train_next (edict_t *self)
 1530 {
 1531         edict_t         *ent;
 1532         vec3_t          dest;
 1533         qboolean        first;
 1534 
 1535         first = true;
 1536 again:
 1537         if (!self->target)
 1538         {
 1539 //              gi.dprintf ("train_next: no next target\n");
 1540                 return;
 1541         }
 1542 
 1543         ent = G_PickTarget (self->target);
 1544         if (!ent)
 1545         {
 1546                 gi.dprintf ("train_next: bad target %s\n", self->target);
 1547                 return;
 1548         }
 1549 
 1550         self->target = ent->target;
 1551 
 1552         // check for a teleport path_corner
 1553         if (ent->spawnflags & 1)
 1554         {
 1555                 if (!first)
 1556                 {
 1557                         gi.dprintf ("connected teleport path_corners, see %s at %s\n", ent->classname, vtos(ent->s.origin));
 1558                         return;
 1559                 }
 1560                 first = false;
 1561                 VectorSubtract (ent->s.origin, self->mins, self->s.origin);
 1562                 VectorCopy (self->s.origin, self->s.old_origin);
 1563                 self->s.event = EV_OTHER_TELEPORT;
 1564                 gi.linkentity (self);
 1565                 goto again;
 1566         }
 1567 
 1568         self->moveinfo.wait = ent->wait;
 1569         self->target_ent = ent;
 1570 
 1571         if (!(self->flags & FL_TEAMSLAVE))
 1572         {
 1573                 if (self->moveinfo.sound_start)
 1574                         gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
 1575                 self->s.sound = self->moveinfo.sound_middle;
 1576         }
 1577 
 1578         VectorSubtract (ent->s.origin, self->mins, dest);
 1579         self->moveinfo.state = STATE_TOP;
 1580         VectorCopy (self->s.origin, self->moveinfo.start_origin);
 1581         VectorCopy (dest, self->moveinfo.end_origin);
 1582         Move_Calc (self, dest, train_wait);
 1583         self->spawnflags |= TRAIN_START_ON;
 1584 }
 1585 
 1586 void train_resume (edict_t *self)
 1587 {
 1588         edict_t *ent;
 1589         vec3_t  dest;
 1590 
 1591         ent = self->target_ent;
 1592 
 1593         VectorSubtract (ent->s.origin, self->mins, dest);
 1594         self->moveinfo.state = STATE_TOP;
 1595         VectorCopy (self->s.origin, self->moveinfo.start_origin);
 1596         VectorCopy (dest, self->moveinfo.end_origin);
 1597         Move_Calc (self, dest, train_wait);
 1598         self->spawnflags |= TRAIN_START_ON;
 1599 }
 1600 
 1601 void func_train_find (edict_t *self)
 1602 {
 1603         edict_t *ent;
 1604 
 1605         if (!self->target)
 1606         {
 1607                 gi.dprintf ("train_find: no target\n");
 1608                 return;
 1609         }
 1610         ent = G_PickTarget (self->target);
 1611         if (!ent)
 1612         {
 1613                 gi.dprintf ("train_find: target %s not found\n", self->target);
 1614                 return;
 1615         }
 1616         self->target = ent->target;
 1617 
 1618         VectorSubtract (ent->s.origin, self->mins, self->s.origin);
 1619         gi.linkentity (self);
 1620 
 1621         // if not triggered, start immediately
 1622         if (!self->targetname)
 1623                 self->spawnflags |= TRAIN_START_ON;
 1624 
 1625         if (self->spawnflags & TRAIN_START_ON)
 1626         {
 1627                 self->nextthink = level.time + FRAMETIME;
 1628                 self->think = train_next;
 1629                 self->activator = self;
 1630         }
 1631 }
 1632 
 1633 void train_use (edict_t *self, edict_t *other, edict_t *activator)
 1634 {
 1635         self->activator = activator;
 1636 
 1637         if (self->spawnflags & TRAIN_START_ON)
 1638         {
 1639                 if (!(self->spawnflags & TRAIN_TOGGLE))
 1640                         return;
 1641                 self->spawnflags &= ~TRAIN_START_ON;
 1642                 VectorClear (self->velocity);
 1643                 self->nextthink = 0;
 1644         }
 1645         else
 1646         {
 1647                 if (self->target_ent)
 1648                         train_resume(self);
 1649                 else
 1650                         train_next(self);
 1651         }
 1652 }
 1653 
 1654 void SP_func_train (edict_t *self)
 1655 {
 1656         self->movetype = MOVETYPE_PUSH;
 1657 
 1658         VectorClear (self->s.angles);
 1659         self->blocked = train_blocked;
 1660         if (self->spawnflags & TRAIN_BLOCK_STOPS)
 1661                 self->dmg = 0;
 1662         else
 1663         {
 1664                 if (!self->dmg)
 1665                         self->dmg = 100;
 1666         }
 1667         self->solid = SOLID_BSP;
 1668         gi.setmodel (self, self->model);
 1669 
 1670         if (st.noise)
 1671                 self->moveinfo.sound_middle = gi.soundindex  (st.noise);
 1672 
 1673         if (!self->speed)
 1674                 self->speed = 100;
 1675 
 1676         self->moveinfo.speed = self->speed;
 1677         self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed;
 1678 
 1679         self->use = train_use;
 1680 
 1681         gi.linkentity (self);
 1682 
 1683         if (self->target)
 1684         {
 1685                 // start trains on the second frame, to make sure their targets have had
 1686                 // a chance to spawn
 1687                 self->nextthink = level.time + FRAMETIME;
 1688                 self->think = func_train_find;
 1689         }
 1690         else
 1691         {
 1692                 gi.dprintf ("func_train without a target at %s\n", vtos(self->absmin));
 1693         }
 1694 }
 1695 
 1696 
 1697 /*QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8)
 1698 */
 1699 void trigger_elevator_use (edict_t *self, edict_t *other, edict_t *activator)
 1700 {
 1701         edict_t *target;
 1702 
 1703         if (self->movetarget->nextthink)
 1704         {
 1705 //              gi.dprintf("elevator busy\n");
 1706                 return;
 1707         }
 1708 
 1709         if (!other->pathtarget)
 1710         {
 1711                 gi.dprintf("elevator used with no pathtarget\n");
 1712                 return;
 1713         }
 1714 
 1715         target = G_PickTarget (other->pathtarget);
 1716         if (!target)
 1717         {
 1718                 gi.dprintf("elevator used with bad pathtarget: %s\n", other->pathtarget);
 1719                 return;
 1720         }
 1721 
 1722         self->movetarget->target_ent = target;
 1723         train_resume (self->movetarget);
 1724 }
 1725 
 1726 void trigger_elevator_init (edict_t *self)
 1727 {
 1728         if (!self->target)
 1729         {
 1730                 gi.dprintf("trigger_elevator has no target\n");
 1731                 return;
 1732         }
 1733         self->movetarget = G_PickTarget (self->target);
 1734         if (!self->movetarget)
 1735         {
 1736                 gi.dprintf("trigger_elevator unable to find target %s\n", self->target);
 1737                 return;
 1738         }
 1739         if (strcmp(self->movetarget->classname, "func_train") != 0)
 1740         {
 1741                 gi.dprintf("trigger_elevator target %s is not a train\n", self->target);
 1742                 return;
 1743         }
 1744 
 1745         self->use = trigger_elevator_use;
 1746         self->svflags = SVF_NOCLIENT;
 1747 
 1748 }
 1749 
 1750 void SP_trigger_elevator (edict_t *self)
 1751 {
 1752         self->think = trigger_elevator_init;
 1753         self->nextthink = level.time + FRAMETIME;
 1754 }
 1755 
 1756 
 1757 /*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON
 1758 "wait"                  base time between triggering all targets, default is 1
 1759 "random"                wait variance, default is 0
 1760 
 1761 so, the basic time between firing is a random time between
 1762 (wait - random) and (wait + random)
 1763 
 1764 "delay"                 delay before first firing when turned on, default is 0
 1765 
 1766 "pausetime"             additional delay used only the very first time
 1767                                 and only if spawned with START_ON
 1768 
 1769 These can used but not touched.
 1770 */
 1771 void func_timer_think (edict_t *self)
 1772 {
 1773         G_UseTargets (self, self->activator);
 1774         self->nextthink = level.time + self->wait + crandom() * self->random;
 1775 }
 1776 
 1777 void func_timer_use (edict_t *self, edict_t *other, edict_t *activator)
 1778 {
 1779         self->activator = activator;
 1780 
 1781         // if on, turn it off
 1782         if (self->nextthink)
 1783         {
 1784                 self->nextthink = 0;
 1785                 return;
 1786         }
 1787 
 1788         // turn it on
 1789         if (self->delay)
 1790                 self->nextthink = level.time + self->delay;
 1791         else
 1792                 func_timer_think (self);
 1793 }
 1794 
 1795 void SP_func_timer (edict_t *self)
 1796 {
 1797         if (!self->wait)
 1798                 self->wait = 1.0;
 1799 
 1800         self->use = func_timer_use;
 1801         self->think = func_timer_think;
 1802 
 1803         if (self->random >= self->wait)
 1804         {
 1805                 self->random = self->wait - FRAMETIME;
 1806                 gi.dprintf("func_timer at %s has random >= wait\n", vtos(self->s.origin));
 1807         }
 1808 
 1809         if (self->spawnflags & 1)
 1810         {
 1811                 self->nextthink = level.time + 1.0 + st.pausetime + self->delay + self->wait + crandom() * self->random;
 1812                 self->activator = self;
 1813         }
 1814 
 1815         self->svflags = SVF_NOCLIENT;
 1816 }
 1817 
 1818 
 1819 /*QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE
 1820 Conveyors are stationary brushes that move what's on them.
 1821 The brush should be have a surface with at least one current content enabled.
 1822 speed   default 100
 1823 */
 1824 
 1825 void func_conveyor_use (edict_t *self, edict_t *other, edict_t *activator)
 1826 {
 1827         if (self->spawnflags & 1)
 1828         {
 1829                 self->speed = 0;
 1830                 self->spawnflags &= ~1;
 1831         }
 1832         else
 1833         {
 1834                 self->speed = self->count;
 1835                 self->spawnflags |= 1;
 1836         }
 1837 
 1838         if (!(self->spawnflags & 2))
 1839                 self->count = 0;
 1840 }
 1841 
 1842 void SP_func_conveyor (edict_t *self)
 1843 {
 1844         if (!self->speed)
 1845                 self->speed = 100;
 1846 
 1847         if (!(self->spawnflags & 1))
 1848         {
 1849                 self->count = self->speed;
 1850                 self->speed = 0;
 1851         }
 1852 
 1853         self->use = func_conveyor_use;
 1854 
 1855         gi.setmodel (self, self->model);
 1856         self->solid = SOLID_BSP;
 1857         gi.linkentity (self);
 1858 }
 1859 
 1860 
 1861 /*QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down
 1862 A secret door.  Slide back and then to the side.
 1863 
 1864 open_once               doors never closes
 1865 1st_left                1st move is left of arrow
 1866 1st_down                1st move is down from arrow
 1867 always_shoot    door is shootebale even if targeted
 1868 
 1869 "angle"         determines the direction
 1870 "dmg"           damage to inflic when blocked (default 2)
 1871 "wait"          how long to hold in the open position (default 5, -1 means hold)
 1872 */
 1873 
 1874 #define SECRET_ALWAYS_SHOOT     1
 1875 #define SECRET_1ST_LEFT         2
 1876 #define SECRET_1ST_DOWN         4
 1877 
 1878 void door_secret_move1 (edict_t *self);
 1879 void door_secret_move2 (edict_t *self);
 1880 void door_secret_move3 (edict_t *self);
 1881 void door_secret_move4 (edict_t *self);
 1882 void door_secret_move5 (edict_t *self);
 1883 void door_secret_move6 (edict_t *self);
 1884 void door_secret_done (edict_t *self);
 1885 
 1886 void door_secret_use (edict_t *self, edict_t *other, edict_t *activator)
 1887 {
 1888         // make sure we're not already moving
 1889         if (!VectorCompare(self->s.origin, vec3_origin))
 1890                 return;
 1891 
 1892         Move_Calc (self, self->pos1, door_secret_move1);
 1893         door_use_areaportals (self, true);
 1894 }
 1895 
 1896 void door_secret_move1 (edict_t *self)
 1897 {
 1898         self->nextthink = level.time + 1.0;
 1899         self->think = door_secret_move2;
 1900 }
 1901 
 1902 void door_secret_move2 (edict_t *self)
 1903 {
 1904         Move_Calc (self, self->pos2, door_secret_move3);
 1905 }
 1906 
 1907 void door_secret_move3 (edict_t *self)
 1908 {
 1909         if (self->wait == -1)
 1910                 return;
 1911         self->nextthink = level.time + self->wait;
 1912         self->think = door_secret_move4;
 1913 }
 1914 
 1915 void door_secret_move4 (edict_t *self)
 1916 {
 1917         Move_Calc (self, self->pos1, door_secret_move5);
 1918 }
 1919 
 1920 void door_secret_move5 (edict_t *self)
 1921 {
 1922         self->nextthink = level.time + 1.0;
 1923         self->think = door_secret_move6;
 1924 }
 1925 
 1926 void door_secret_move6 (edict_t *self)
 1927 {
 1928         Move_Calc (self, vec3_origin, door_secret_done);
 1929 }
 1930 
 1931 void door_secret_done (edict_t *self)
 1932 {
 1933         if (!(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT))
 1934         {
 1935                 self->health = 0;
 1936                 self->takedamage = DAMAGE_YES;
 1937         }
 1938         door_use_areaportals (self, false);
 1939 }
 1940 
 1941 void door_secret_blocked  (edict_t *self, edict_t *other)
 1942 {
 1943         if (!(other->svflags & SVF_MONSTER) && (!other->client) )
 1944         {
 1945                 // give it a chance to go away on it's own terms (like gibs)
 1946                 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
 1947                 // if it's still there, nuke it
 1948                 if (other)
 1949                         BecomeExplosion1 (other);
 1950                 return;
 1951         }
 1952 
 1953         if (level.time < self->touch_debounce_time)
 1954                 return;
 1955         self->touch_debounce_time = level.time + 0.5;
 1956 
 1957         T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
 1958 }
 1959 
 1960 void door_secret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
 1961 {
 1962         self->takedamage = DAMAGE_NO;
 1963         door_secret_use (self, attacker, attacker);
 1964 }
 1965 
 1966 void SP_func_door_secret (edict_t *ent)
 1967 {
 1968         vec3_t  forward, right, up;
 1969         float   side;
 1970         float   width;
 1971         float   length;
 1972 
 1973         ent->moveinfo.sound_start = gi.soundindex  ("doors/dr1_strt.wav");
 1974         ent->moveinfo.sound_middle = gi.soundindex  ("doors/dr1_mid.wav");
 1975         ent->moveinfo.sound_end = gi.soundindex  ("doors/dr1_end.wav");
 1976 
 1977         ent->movetype = MOVETYPE_PUSH;
 1978         ent->solid = SOLID_BSP;
 1979         gi.setmodel (ent, ent->model);
 1980 
 1981         ent->blocked = door_secret_blocked;
 1982         ent->use = door_secret_use;
 1983 
 1984         if (!(ent->targetname) || (ent->spawnflags & SECRET_ALWAYS_SHOOT))
 1985         {
 1986                 ent->health = 0;
 1987                 ent->takedamage = DAMAGE_YES;
 1988                 ent->die = door_secret_die;
 1989         }
 1990 
 1991         if (!ent->dmg)
 1992                 ent->dmg = 2;
 1993 
 1994         if (!ent->wait)
 1995                 ent->wait = 5;
 1996 
 1997         ent->moveinfo.accel =
 1998         ent->moveinfo.decel =
 1999         ent->moveinfo.speed = 50;
 2000 
 2001         // calculate positions
 2002         AngleVectors (ent->s.angles, forward, right, up);
 2003         VectorClear (ent->s.angles);
 2004         side = 1.0 - (ent->spawnflags & SECRET_1ST_LEFT);
 2005         if (ent->spawnflags & SECRET_1ST_DOWN)
 2006                 width = fabs(DotProduct(up, ent->size));
 2007         else
 2008                 width = fabs(DotProduct(right, ent->size));
 2009         length = fabs(DotProduct(forward, ent->size));
 2010         if (ent->spawnflags & SECRET_1ST_DOWN)
 2011                 VectorMA (ent->s.origin, -1 * width, up, ent->pos1);
 2012         else
 2013                 VectorMA (ent->s.origin, side * width, right, ent->pos1);
 2014         VectorMA (ent->pos1, length, forward, ent->pos2);
 2015 
 2016         if (ent->health)
 2017         {
 2018                 ent->takedamage = DAMAGE_YES;
 2019                 ent->die = door_killed;
 2020                 ent->max_health = ent->health;
 2021         }
 2022         else if (ent->targetname && ent->message)
 2023         {
 2024                 gi.soundindex ("misc/talk.wav");
 2025                 ent->touch = door_touch;
 2026         }
 2027         
 2028         ent->classname = "func_door";
 2029 
 2030         gi.linkentity (ent);
 2031 }
 2032 
 2033 
 2034 /*QUAKED func_killbox (1 0 0) ?
 2035 Kills everything inside when fired, irrespective of protection.
 2036 */
 2037 void use_killbox (edict_t *self, edict_t *other, edict_t *activator)
 2038 {
 2039         KillBox (self);
 2040 }
 2041 
 2042 void SP_func_killbox (edict_t *ent)
 2043 {
 2044         gi.setmodel (ent, ent->model);
 2045         ent->use = use_killbox;
 2046         ent->svflags = SVF_NOCLIENT;
 2047 }
 2048 
 2049